Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/enclave-model-api-example/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ SYFT_ENCLAVE_DATA_OWNERS,\
SYFT_ENCLAVE_REQUIRE_TEE,\
SYFT_ENCLAVE_FRESH_STATE,\
SYFT_ENCLAVE_USE_ENCRYPTION,\
SYFT_ENCLAVE_PERSIST_OWNER_STATE,\
SYFT_DEFAULT_JOB_TIMEOUT_SECONDS,\
SYFT_BOOTSTRAP,\
SYFT_BOOTSTRAP_WIF_AUDIENCE,\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def main() -> None:
f"Enclave settings — email={settings.email} data_owners={settings.data_owners} "
f"token_path={settings.token_path} poll_interval={settings.poll_interval}s "
f"require_tee={settings.require_tee} fresh_state={settings.fresh_state} "
f"use_encryption={settings.use_encryption}"
f"use_encryption={settings.use_encryption} "
f"persist_owner_state={settings.persist_owner_state}"
)
logger.info(
f"Inference settings — model_owner={inference.model_owner} "
Expand All @@ -62,6 +63,7 @@ def main() -> None:
token_path=settings.token_path,
data_owners=settings.data_owners,
encryption=settings.use_encryption,
persist_owner_state=settings.persist_owner_state,
)
logger.info("SyftEnclaveClient ready")

Expand Down
1 change: 1 addition & 0 deletions packages/syft-enclave/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ SYFT_ENCLAVE_DATA_OWNERS,\
SYFT_ENCLAVE_REQUIRE_TEE,\
SYFT_ENCLAVE_FRESH_STATE,\
SYFT_ENCLAVE_USE_ENCRYPTION,\
SYFT_ENCLAVE_PERSIST_OWNER_STATE,\
SYFT_DEFAULT_JOB_TIMEOUT_SECONDS,\
SYFT_BOOTSTRAP,\
SYFT_BOOTSTRAP_WIF_AUDIENCE,\
Expand Down
4 changes: 3 additions & 1 deletion packages/syft-enclave/src/syft_enclaves/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def main() -> None:
f"Enclave settings — email={settings.email} data_owners={settings.data_owners} "
f"token_path={settings.token_path} poll_interval={settings.poll_interval}s "
f"require_tee={settings.require_tee} fresh_state={settings.fresh_state} "
f"use_encryption={settings.use_encryption}"
f"use_encryption={settings.use_encryption} "
f"persist_owner_state={settings.persist_owner_state}"
)

logger.info("Building SyftEnclaveClient...")
Expand All @@ -52,6 +53,7 @@ def main() -> None:
token_path=settings.token_path,
data_owners=settings.data_owners,
encryption=settings.use_encryption,
persist_owner_state=settings.persist_owner_state,
)
logger.info("SyftEnclaveClient ready")

Expand Down
6 changes: 6 additions & 0 deletions packages/syft-enclave/src/syft_enclaves/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,20 +435,26 @@ def for_enclave(
token_path: Path | str | None = None,
data_owners: list[str] | None = None,
encryption: bool = False,
persist_owner_state: bool = False,
) -> "SyftEnclaveClient":
"""Build an enclave client backed by a real Google Drive connection.
Args:
email: The enclave datasite's email address.
token_path: Path to a pre-authorized Google Drive OAuth token.
data_owners: Emails whose approval gates every job on this enclave.
encryption: Enable end-to-end drive encryption.
persist_owner_state: Keep the owner-only state used to restore
this datasite later (event log, rolling state, checkpoints).
Off by default - an enclave gets ephemeral keys and wipes its
state on boot, so there is never anything to restore.
"""
config = SyftRDSClientConfig.for_jupyter(
email=email,
has_ds_role=True,
has_do_role=True,
token_path=Path(token_path) if token_path is not None else None,
encryption=encryption,
persist_owner_state=persist_owner_state,
)

# Note: We do not currently provide the ability to load encryption keys passed during creation of enclave.
Expand Down
13 changes: 13 additions & 0 deletions packages/syft-enclave/src/syft_enclaves/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,16 @@ def _split_data_owners(cls, v: object) -> object:
"Enabled by default; set false to disable."
),
)
persist_owner_state: bool = Field(
default=False,
description=(
"Keep the owner-only state that exists solely to restore this "
"datasite later: the append-only event log, the rolling state and "
"the checkpoints. Off by default because an enclave can never "
"restore — it gets an ephemeral keypair each boot and wipes its "
"state under fresh_state — so those writes only spend Drive API "
"calls inside the poll loop. Peer-facing state (outbox, "
"collections, peers) is unaffected. Set true only for a stateful "
"enclave (fresh_state=false)."
),
)
34 changes: 34 additions & 0 deletions packages/syft-enclave/tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def test_defaults_applied_when_required_fields_set(required_env):
assert settings.log_level == "INFO"
assert settings.fresh_state is True # default: always start with a clean slate
assert settings.use_encryption is True # default: encryption on
assert settings.persist_owner_state is False # default: an enclave never restores


def test_data_owners_parsed_from_comma_separated_string(clean_env):
Expand Down Expand Up @@ -112,3 +113,36 @@ def test_use_encryption_can_be_disabled_via_env(required_env):
required_env.setenv("SYFT_ENCLAVE_USE_ENCRYPTION", "false")
settings = EnclaveSettings(_env_file=None)
assert settings.use_encryption is False


def test_persist_owner_state_can_be_enabled_via_env(required_env):
required_env.setenv("SYFT_ENCLAVE_PERSIST_OWNER_STATE", "true")
settings = EnclaveSettings(_env_file=None)
assert settings.persist_owner_state is True


def test_persist_owner_state_reaches_the_do_syncer_config():
"""Guard the `**kw` hole between for_enclave and the sync engine.

``SyftRDSClientConfig.for_jupyter`` forwards unknown kwargs straight to
``SyftboxManagerConfig.for_jupyter``, so nothing type-checks this hop. A
rename on either side would silently give an enclave back the owner state
it is not supposed to keep.
"""
from syft_rds.config import SyftRDSClientConfig

config = SyftRDSClientConfig.for_jupyter(
email="enclave@openmined.org",
has_do_role=True,
has_ds_role=True,
persist_owner_state=False,
)
assert config.sync.persist_owner_state is False
assert config.sync.datasite_owner_syncer_config.persist_owner_state is False

default = SyftRDSClientConfig.for_jupyter(
email="enclave@openmined.org",
has_do_role=True,
has_ds_role=True,
)
assert default.sync.datasite_owner_syncer_config.persist_owner_state is True
32 changes: 31 additions & 1 deletion syft/sync/syftbox_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ class SyftboxManagerConfig(BaseModel):
has_ds_role: bool = False
has_do_role: bool = False
use_in_memory_cache: bool = True
# Keep the owner-only state used to restore this datasite later: event
# log, rolling state, checkpoints. Off for datasites that can never
# restore (see syft-enclave's EnclaveSettings).
persist_owner_state: bool = True

datasite_owner_syncer_config: DatasiteOwnerSyncerConfig
peer_manager_config: PeerManagerConfig
Expand All @@ -126,6 +130,7 @@ def for_colab(
has_ds_role: bool = False,
has_do_role: bool = False,
encryption: bool = False,
persist_owner_state: bool = True,
crypto_keys_path: Path | None = None,
skip_peer_on_patch_version_diff: Optional[
bool
Expand All @@ -150,6 +155,7 @@ def for_colab(
connection_configs = [GdriveConnectionConfig(email=email, token_path=None)]
datasite_owner_syncer_config = DatasiteOwnerSyncerConfig(
email=email,
persist_owner_state=persist_owner_state,
syftbox_folder=syftbox_folder,
collections_folder=collections_folder,
collection_specs=collection_specs,
Expand Down Expand Up @@ -192,6 +198,7 @@ def for_colab(
has_do_role=has_do_role,
connection_configs=connection_configs,
use_in_memory_cache=False,
persist_owner_state=persist_owner_state,
datasite_owner_syncer_config=datasite_owner_syncer_config,
datasite_watcher_syncer_config=datasite_watcher_syncer_config,
peer_manager_config=peer_manager_config,
Expand All @@ -205,6 +212,7 @@ def for_jupyter(
has_do_role: bool = False,
token_path: Path | None = None,
encryption: bool = False,
persist_owner_state: bool = True,
crypto_keys_path: Path | None = None,
skip_peer_on_patch_version_diff: Optional[
bool
Expand Down Expand Up @@ -232,6 +240,7 @@ def for_jupyter(
]
datasite_owner_syncer_config = DatasiteOwnerSyncerConfig(
email=email,
persist_owner_state=persist_owner_state,
syftbox_folder=syftbox_folder,
collections_folder=collections_folder,
collection_specs=collection_specs,
Expand Down Expand Up @@ -274,6 +283,7 @@ def for_jupyter(
has_ds_role=has_ds_role,
has_do_role=has_do_role,
use_in_memory_cache=False,
persist_owner_state=persist_owner_state,
datasite_owner_syncer_config=datasite_owner_syncer_config,
datasite_watcher_syncer_config=datasite_watcher_syncer_config,
peer_manager_config=peer_manager_config,
Expand All @@ -288,6 +298,7 @@ def _base_config_for_testing(
has_ds_role: bool = False,
has_do_role: bool = False,
use_in_memory_cache: bool = True,
persist_owner_state: bool = True,
check_versions: bool = False,
collection_specs: list["CollectionSyncSpec"] | None = None,
):
Expand All @@ -306,6 +317,7 @@ def _base_config_for_testing(

datasite_owner_syncer_config = DatasiteOwnerSyncerConfig(
email=email,
persist_owner_state=persist_owner_state,
syftbox_folder=syftbox_folder,
collections_folder=collections_folder,
collection_specs=collection_specs,
Expand Down Expand Up @@ -346,6 +358,7 @@ def _base_config_for_testing(
has_ds_role=has_ds_role,
has_do_role=has_do_role,
use_in_memory_cache=use_in_memory_cache,
persist_owner_state=persist_owner_state,
datasite_owner_syncer_config=datasite_owner_syncer_config,
datasite_watcher_syncer_config=datasite_watcher_syncer_config,
peer_manager_config=peer_manager_config,
Expand All @@ -361,6 +374,7 @@ def for_google_drive_testing_connection(
has_ds_role: bool = False,
has_do_role: bool = False,
use_in_memory_cache: bool = True,
persist_owner_state: bool = True,
check_versions: bool = False,
collection_specs: list["CollectionSyncSpec"] | None = None,
):
Expand All @@ -381,6 +395,7 @@ def for_google_drive_testing_connection(
]
datasite_owner_syncer_config = DatasiteOwnerSyncerConfig(
email=email,
persist_owner_state=persist_owner_state,
syftbox_folder=syftbox_folder,
collections_folder=collections_folder,
collection_specs=collection_specs,
Expand Down Expand Up @@ -418,6 +433,7 @@ def for_google_drive_testing_connection(
email=email,
syftbox_folder=syftbox_folder,
write_files=write_files,
persist_owner_state=persist_owner_state,
datasite_owner_syncer_config=datasite_owner_syncer_config,
datasite_watcher_syncer_config=datasite_watcher_syncer_config,
has_ds_role=has_ds_role,
Expand Down Expand Up @@ -587,6 +603,7 @@ def for_colab(
has_ds_role: bool = False,
has_do_role: bool = False,
encryption: bool = False,
persist_owner_state: bool = True,
crypto_keys_path: Path | None = None,
skip_peer_on_patch_version_diff: Optional[
bool
Expand All @@ -599,6 +616,7 @@ def for_colab(
has_ds_role=has_ds_role,
has_do_role=has_do_role,
encryption=encryption,
persist_owner_state=persist_owner_state,
crypto_keys_path=crypto_keys_path,
skip_peer_on_patch_version_diff=skip_peer_on_patch_version_diff,
force_ignore_peer_version=force_ignore_peer_version,
Expand All @@ -614,6 +632,7 @@ def for_jupyter(
has_do_role: bool = False,
token_path: Path | None = None,
encryption: bool = False,
persist_owner_state: bool = True,
crypto_keys_path: Path | None = None,
skip_peer_on_patch_version_diff: Optional[
bool
Expand All @@ -629,6 +648,7 @@ def for_jupyter(
has_do_role=has_do_role,
token_path=token_path,
encryption=encryption,
persist_owner_state=persist_owner_state,
crypto_keys_path=crypto_keys_path,
skip_peer_on_patch_version_diff=skip_peer_on_patch_version_diff,
force_ignore_peer_version=force_ignore_peer_version,
Expand All @@ -648,6 +668,7 @@ def _pair_with_google_drive_testing_connection(
add_peers: bool = True,
load_peers: bool = False,
use_in_memory_cache: bool = True,
persist_owner_state: bool = True,
clear_caches: bool = True,
check_versions: bool = False,
collection_specs: list["CollectionSyncSpec"] | None = None,
Expand All @@ -656,6 +677,7 @@ def _pair_with_google_drive_testing_connection(
email=do_email,
syftbox_folder=base_path1,
use_in_memory_cache=use_in_memory_cache,
persist_owner_state=persist_owner_state,
token_path=do_token_path,
has_ds_role=False,
has_do_role=True,
Expand All @@ -669,6 +691,7 @@ def _pair_with_google_drive_testing_connection(
email=ds_email,
syftbox_folder=base_path2,
use_in_memory_cache=use_in_memory_cache,
persist_owner_state=persist_owner_state,
token_path=ds_token_path,
has_ds_role=True,
has_do_role=False,
Expand Down Expand Up @@ -732,6 +755,7 @@ def pair_with_mock_drive_service_connection(
sync_automatically: bool = False,
add_peers: bool = True,
use_in_memory_cache: bool = True,
persist_owner_state: bool = True,
check_versions: bool = False,
encryption: bool = False,
collection_specs: list["CollectionSyncSpec"] | None = None,
Expand All @@ -750,6 +774,7 @@ def pair_with_mock_drive_service_connection(
sync_automatically: Whether to sync when DS sends changes
add_peers: Whether to automatically add and approve peers
use_in_memory_cache: Whether to use in-memory caches
persist_owner_state: Whether the DO keeps restorable owner state
check_versions: Whether to check protocol/client versions

Returns:
Expand All @@ -762,6 +787,7 @@ def pair_with_mock_drive_service_connection(
has_ds_role=False,
has_do_role=True,
use_in_memory_cache=use_in_memory_cache,
persist_owner_state=persist_owner_state,
check_versions=check_versions,
collection_specs=collection_specs,
)
Expand All @@ -772,6 +798,7 @@ def pair_with_mock_drive_service_connection(
has_ds_role=True,
has_do_role=False,
use_in_memory_cache=use_in_memory_cache,
persist_owner_state=persist_owner_state,
check_versions=check_versions,
collection_specs=collection_specs,
)
Expand Down Expand Up @@ -933,7 +960,10 @@ def sync(

Args:
auto_checkpoint: If True, automatically create checkpoint when
event count exceeds threshold (DO only).
event count exceeds threshold (DO only). Has no
effect when this manager was built with
persist_owner_state=False, which keeps no
checkpoints at all.
checkpoint_threshold: Create checkpoint when events >= this value.
auto_compact: If True, after each DO sync, compact each peer's
outbox if it holds at least `compact_threshold`
Expand Down
Loading
Loading