Skip to content

Commit

Permalink
checkpoint: into main from release/2.3.0 @ cfa218d (#17895)
Browse files Browse the repository at this point in the history
Source hash: cfa218d
Remaining commits: 1
  • Loading branch information
pmaslana committed Apr 26, 2024
2 parents d9612ba + c4f0cc7 commit 87a5d24
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 15 deletions.
77 changes: 77 additions & 0 deletions chia/_tests/cmds/test_daemon.py
@@ -0,0 +1,77 @@
from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, Optional

import pytest
from _pytest.capture import CaptureFixture
from click.testing import CliRunner
from pytest_mock import MockerFixture

from chia.cmds.chia import cli
from chia.cmds.start_funcs import create_start_daemon_connection


@pytest.mark.anyio
@pytest.mark.parametrize("skip_keyring", [False, True])
@pytest.mark.parametrize("unlock_keyring", [False, True])
async def test_daemon(
skip_keyring: bool, unlock_keyring: bool, mocker: MockerFixture, capsys: CaptureFixture[str]
) -> None:
class DummyConnection:
@staticmethod
async def is_keyring_locked() -> bool:
return unlock_keyring

@staticmethod
async def unlock_keyring(_passphrase: str) -> bool:
return True

async def connect_to_daemon_and_validate(_root_path: Path, _config: Dict[str, Any]) -> DummyConnection:
return DummyConnection()

class DummyKeychain:
@staticmethod
def get_cached_master_passphrase() -> Optional[str]:
return None

def get_current_passphrase() -> Optional[str]:
return "a-passphrase"

mocker.patch("chia.cmds.start_funcs.connect_to_daemon_and_validate", side_effect=connect_to_daemon_and_validate)
mocker.patch("chia.cmds.start_funcs.Keychain", new=DummyKeychain)
mocker.patch("chia.cmds.start_funcs.get_current_passphrase", side_effect=get_current_passphrase)

daemon = await create_start_daemon_connection(Path("/path-not-exist"), {}, skip_keyring=skip_keyring)
assert daemon is not None
captured = capsys.readouterr()
assert captured.err == ""
if skip_keyring:
assert captured.out.endswith("Skipping to unlock keyring\n")
else:
assert not captured.out.endswith("Skipping to unlock keyring\n")


def test_start_daemon(tmp_path: Path, empty_keyring: Any, mocker: MockerFixture) -> None:
class DummyDaemon:
@staticmethod
async def close() -> None:
return None

async def create_start_daemon_connection_dummy(
root_path: Path, config: Dict[str, Any], *, skip_keyring: bool
) -> DummyDaemon:
return DummyDaemon()

mocker.patch(
"chia.cmds.start_funcs.create_start_daemon_connection", side_effect=create_start_daemon_connection_dummy
)

runner = CliRunner()
result = runner.invoke(
cli,
["--root-path", str(tmp_path), "init"],
)
assert result.exit_code == 0
result = runner.invoke(cli, ["--root-path", str(tmp_path), "start", "daemon", "-s"])
assert result.exit_code == 0
2 changes: 1 addition & 1 deletion chia/cmds/sim_funcs.py
Expand Up @@ -291,7 +291,7 @@ async def async_config_wizard(
print("Starting Simulator now...\n\n")

sys.argv[0] = str(Path(sys.executable).parent / "chia") # fix path for tests
await async_start(root_path, config, ("simulator",), False)
await async_start(root_path, config, ("simulator",), restart=False, skip_keyring=False)

# now we make sure the simulator has a genesis block
print("Please wait, generating genesis block.")
Expand Down
5 changes: 3 additions & 2 deletions chia/cmds/start.py
Expand Up @@ -8,9 +8,10 @@

@click.command("start", help="Start service groups")
@click.option("-r", "--restart", is_flag=True, type=bool, help="Restart running services")
@click.option("-s", "--skip-keyring", is_flag=True, type=bool, help="Skip to unlock keyring")
@click.argument("group", type=click.Choice(list(all_groups())), nargs=-1, required=True)
@click.pass_context
def start_cmd(ctx: click.Context, restart: bool, group: tuple[str, ...]) -> None:
def start_cmd(ctx: click.Context, restart: bool, skip_keyring: bool, group: tuple[str, ...]) -> None:
import asyncio

from chia.cmds.beta_funcs import warn_if_beta_enabled
Expand All @@ -20,4 +21,4 @@ def start_cmd(ctx: click.Context, restart: bool, group: tuple[str, ...]) -> None
root_path = ctx.obj["root_path"]
config = load_config(root_path, "config.yaml")
warn_if_beta_enabled(config)
asyncio.run(async_start(root_path, config, group, restart))
asyncio.run(async_start(root_path, config, group, restart, skip_keyring=skip_keyring))
31 changes: 19 additions & 12 deletions chia/cmds/start_funcs.py
Expand Up @@ -31,7 +31,9 @@ def launch_start_daemon(root_path: Path) -> subprocess.Popen:
return process


async def create_start_daemon_connection(root_path: Path, config: Dict[str, Any]) -> Optional[DaemonProxy]:
async def create_start_daemon_connection(
root_path: Path, config: Dict[str, Any], *, skip_keyring: bool
) -> Optional[DaemonProxy]:
connection = await connect_to_daemon_and_validate(root_path, config)
if connection is None:
print("Starting daemon")
Expand All @@ -44,24 +46,29 @@ async def create_start_daemon_connection(root_path: Path, config: Dict[str, Any]
# it prints "daemon: listening"
connection = await connect_to_daemon_and_validate(root_path, config)
if connection:
passphrase = None
if await connection.is_keyring_locked():
passphrase = Keychain.get_cached_master_passphrase()
if passphrase is None or not Keychain.master_passphrase_is_valid(passphrase):
with ThreadPoolExecutor(max_workers=1, thread_name_prefix="get_current_passphrase") as executor:
passphrase = await asyncio.get_running_loop().run_in_executor(executor, get_current_passphrase)
if skip_keyring:
print("Skipping to unlock keyring")
else:
passphrase = None
if await connection.is_keyring_locked():
passphrase = Keychain.get_cached_master_passphrase()
if passphrase is None or not Keychain.master_passphrase_is_valid(passphrase):
with ThreadPoolExecutor(max_workers=1, thread_name_prefix="get_current_passphrase") as executor:
passphrase = await asyncio.get_running_loop().run_in_executor(executor, get_current_passphrase)

if passphrase:
print("Unlocking daemon keyring")
await connection.unlock_keyring(passphrase)
if passphrase:
print("Unlocking daemon keyring")
await connection.unlock_keyring(passphrase)

return connection
return None


async def async_start(root_path: Path, config: Dict[str, Any], group: tuple[str, ...], restart: bool) -> None:
async def async_start(
root_path: Path, config: Dict[str, Any], group: tuple[str, ...], restart: bool, *, skip_keyring: bool
) -> None:
try:
daemon = await create_start_daemon_connection(root_path, config)
daemon = await create_start_daemon_connection(root_path, config, skip_keyring=skip_keyring)
except KeychainMaxUnlockAttempts:
print("Failed to unlock keyring")
return None
Expand Down

0 comments on commit 87a5d24

Please sign in to comment.