2022-03-16 21:47:54 +03:00
|
|
|
import asyncio
|
|
|
|
import logging
|
|
|
|
import signal
|
|
|
|
import sqlite3
|
2022-04-08 03:10:44 +03:00
|
|
|
from pathlib import Path
|
2022-03-16 21:47:54 +03:00
|
|
|
from secrets import token_bytes
|
2022-06-11 09:35:41 +03:00
|
|
|
from typing import AsyncGenerator, List, Optional, Tuple
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-05-03 05:32:37 +03:00
|
|
|
from chia.cmds.init_funcs import init
|
2022-03-16 21:47:54 +03:00
|
|
|
from chia.consensus.constants import ConsensusConstants
|
2022-08-04 20:59:39 +03:00
|
|
|
from chia.daemon.server import WebSocketServer, daemon_launch_lock_path
|
2022-06-11 09:35:41 +03:00
|
|
|
from chia.protocols.shared_protocol import Capability, capabilities
|
2022-07-13 19:43:48 +03:00
|
|
|
from chia.server.start_farmer import create_farmer_service
|
|
|
|
from chia.server.start_full_node import create_full_node_service
|
|
|
|
from chia.server.start_harvester import create_harvester_service
|
|
|
|
from chia.server.start_introducer import create_introducer_service
|
|
|
|
from chia.server.start_timelord import create_timelord_service
|
|
|
|
from chia.server.start_wallet import create_wallet_service
|
2022-07-25 22:07:38 +03:00
|
|
|
from chia.simulator.block_tools import BlockTools
|
2022-07-13 19:43:48 +03:00
|
|
|
from chia.simulator.start_simulator import create_full_node_simulator_service
|
2022-03-16 21:47:54 +03:00
|
|
|
from chia.timelord.timelord_launcher import kill_processes, spawn_process
|
2022-08-10 12:57:34 +03:00
|
|
|
from chia.types.peer_info import PeerInfo
|
2022-03-16 21:47:54 +03:00
|
|
|
from chia.util.bech32m import encode_puzzle_hash
|
2022-05-05 18:16:51 +03:00
|
|
|
from chia.util.config import lock_and_load_config, save_config
|
2022-03-16 21:47:54 +03:00
|
|
|
from chia.util.ints import uint16
|
|
|
|
from chia.util.keychain import bytes_to_mnemonic
|
2022-08-04 20:59:39 +03:00
|
|
|
from chia.util.lock import Lockfile
|
2022-03-16 21:47:54 +03:00
|
|
|
from tests.util.keyring import TempKeyring
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2022-06-11 09:35:41 +03:00
|
|
|
def get_capabilities(disable_capabilities_values: Optional[List[Capability]]) -> List[Tuple[uint16, str]]:
|
|
|
|
if disable_capabilities_values is not None:
|
|
|
|
try:
|
|
|
|
if Capability.BASE in disable_capabilities_values:
|
|
|
|
# BASE capability cannot be removed
|
|
|
|
disable_capabilities_values.remove(Capability.BASE)
|
|
|
|
|
|
|
|
updated_capabilities = []
|
|
|
|
for capability in capabilities:
|
|
|
|
if Capability(int(capability[0])) in disable_capabilities_values:
|
|
|
|
# "0" means capability is disabled
|
|
|
|
updated_capabilities.append((capability[0], "0"))
|
|
|
|
else:
|
|
|
|
updated_capabilities.append(capability)
|
|
|
|
return updated_capabilities
|
|
|
|
except Exception:
|
|
|
|
logging.getLogger(__name__).exception("Error disabling capabilities, defaulting to all capabilities")
|
|
|
|
return capabilities.copy()
|
|
|
|
|
|
|
|
|
2022-03-16 21:47:54 +03:00
|
|
|
async def setup_daemon(btools: BlockTools) -> AsyncGenerator[WebSocketServer, None]:
|
|
|
|
root_path = btools.root_path
|
|
|
|
config = btools.config
|
|
|
|
assert "daemon_port" in config
|
|
|
|
crt_path = root_path / config["daemon_ssl"]["private_crt"]
|
|
|
|
key_path = root_path / config["daemon_ssl"]["private_key"]
|
|
|
|
ca_crt_path = root_path / config["private_ssl_ca"]["crt"]
|
|
|
|
ca_key_path = root_path / config["private_ssl_ca"]["key"]
|
2022-08-04 20:59:39 +03:00
|
|
|
with Lockfile.create(daemon_launch_lock_path(root_path)):
|
|
|
|
shutdown_event = asyncio.Event()
|
|
|
|
ws_server = WebSocketServer(root_path, ca_crt_path, ca_key_path, crt_path, key_path, shutdown_event)
|
|
|
|
await ws_server.start()
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-08-04 20:59:39 +03:00
|
|
|
yield ws_server
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-08-04 20:59:39 +03:00
|
|
|
await ws_server.stop()
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
async def setup_full_node(
|
|
|
|
consensus_constants: ConsensusConstants,
|
2022-05-23 18:13:49 +03:00
|
|
|
db_name: str,
|
2022-03-16 21:47:54 +03:00
|
|
|
self_hostname: str,
|
|
|
|
local_bt: BlockTools,
|
|
|
|
introducer_port=None,
|
|
|
|
simulator=False,
|
|
|
|
send_uncompact_interval=0,
|
|
|
|
sanitize_weight_proof_only=False,
|
|
|
|
connect_to_daemon=False,
|
|
|
|
db_version=1,
|
2022-06-11 09:35:41 +03:00
|
|
|
disable_capabilities: Optional[List[Capability]] = None,
|
2022-08-09 04:05:20 +03:00
|
|
|
yield_service: bool = False,
|
2022-03-16 21:47:54 +03:00
|
|
|
):
|
|
|
|
db_path = local_bt.root_path / f"{db_name}"
|
|
|
|
if db_path.exists():
|
|
|
|
db_path.unlink()
|
|
|
|
|
|
|
|
if db_version > 1:
|
|
|
|
with sqlite3.connect(db_path) as connection:
|
|
|
|
connection.execute("CREATE TABLE database_version(version int)")
|
|
|
|
connection.execute("INSERT INTO database_version VALUES (?)", (db_version,))
|
|
|
|
connection.commit()
|
|
|
|
|
|
|
|
if connect_to_daemon:
|
|
|
|
assert local_bt.config["daemon_port"] is not None
|
2022-07-08 03:57:08 +03:00
|
|
|
config = local_bt.config
|
|
|
|
service_config = config["full_node"]
|
|
|
|
service_config["database_path"] = db_name
|
|
|
|
service_config["send_uncompact_interval"] = send_uncompact_interval
|
|
|
|
service_config["target_uncompact_proofs"] = 30
|
|
|
|
service_config["peer_connect_interval"] = 50
|
|
|
|
service_config["sanitize_weight_proof_only"] = sanitize_weight_proof_only
|
2022-03-16 21:47:54 +03:00
|
|
|
if introducer_port is not None:
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["introducer_peer"]["host"] = self_hostname
|
|
|
|
service_config["introducer_peer"]["port"] = introducer_port
|
2022-03-16 21:47:54 +03:00
|
|
|
else:
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["introducer_peer"] = None
|
|
|
|
service_config["dns_servers"] = []
|
|
|
|
service_config["port"] = 0
|
|
|
|
service_config["rpc_port"] = 0
|
2022-07-16 04:49:40 +03:00
|
|
|
config["simulator"]["auto_farm"] = False # Disable Auto Farm for tests
|
|
|
|
config["simulator"]["use_current_time"] = False # Disable Real timestamps when running tests
|
2022-07-08 03:57:08 +03:00
|
|
|
overrides = service_config["network_overrides"]["constants"][service_config["selected_network"]]
|
2022-03-16 21:47:54 +03:00
|
|
|
updated_constants = consensus_constants.replace_str_to_bytes(**overrides)
|
2022-07-16 04:49:40 +03:00
|
|
|
local_bt.change_config(config)
|
2022-07-13 19:43:48 +03:00
|
|
|
override_capabilities = None if disable_capabilities is None else get_capabilities(disable_capabilities)
|
2022-03-16 21:47:54 +03:00
|
|
|
if simulator:
|
2022-07-13 19:43:48 +03:00
|
|
|
service = create_full_node_simulator_service(
|
|
|
|
local_bt.root_path,
|
|
|
|
config,
|
|
|
|
local_bt,
|
|
|
|
connect_to_daemon=connect_to_daemon,
|
|
|
|
override_capabilities=override_capabilities,
|
|
|
|
)
|
2022-03-16 21:47:54 +03:00
|
|
|
else:
|
2022-07-13 19:43:48 +03:00
|
|
|
service = create_full_node_service(
|
|
|
|
local_bt.root_path,
|
|
|
|
config,
|
|
|
|
updated_constants,
|
|
|
|
connect_to_daemon=connect_to_daemon,
|
|
|
|
override_capabilities=override_capabilities,
|
|
|
|
)
|
2022-03-16 21:47:54 +03:00
|
|
|
await service.start()
|
|
|
|
|
2022-08-09 04:05:20 +03:00
|
|
|
# TODO, just always yield the service only and adjust all other places
|
|
|
|
if yield_service:
|
|
|
|
yield service
|
|
|
|
else:
|
|
|
|
yield service._api
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
service.stop()
|
|
|
|
await service.wait_closed()
|
|
|
|
if db_path.exists():
|
|
|
|
db_path.unlink()
|
|
|
|
|
|
|
|
|
|
|
|
# Note: convert these setup functions to fixtures, or push it one layer up,
|
|
|
|
# keeping these usable independently?
|
|
|
|
async def setup_wallet_node(
|
|
|
|
self_hostname: str,
|
|
|
|
consensus_constants: ConsensusConstants,
|
|
|
|
local_bt: BlockTools,
|
2022-08-31 01:44:27 +03:00
|
|
|
spam_filter_after_n_txs=200,
|
|
|
|
xch_spam_amount=1000000,
|
2022-03-16 21:47:54 +03:00
|
|
|
full_node_port=None,
|
|
|
|
introducer_port=None,
|
|
|
|
key_seed=None,
|
|
|
|
initial_num_public_keys=5,
|
2022-08-09 04:05:20 +03:00
|
|
|
yield_service: bool = False,
|
2022-03-16 21:47:54 +03:00
|
|
|
):
|
|
|
|
with TempKeyring(populate=True) as keychain:
|
2022-07-08 03:57:08 +03:00
|
|
|
config = local_bt.config
|
|
|
|
service_config = config["wallet"]
|
|
|
|
service_config["port"] = 0
|
|
|
|
service_config["rpc_port"] = 0
|
|
|
|
service_config["initial_num_public_keys"] = initial_num_public_keys
|
2022-08-31 01:44:27 +03:00
|
|
|
service_config["spam_filter_after_n_txs"] = spam_filter_after_n_txs
|
|
|
|
service_config["xch_spam_amount"] = xch_spam_amount
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
entropy = token_bytes(32)
|
|
|
|
if key_seed is None:
|
|
|
|
key_seed = entropy
|
2022-08-06 17:50:33 +03:00
|
|
|
keychain.add_private_key(bytes_to_mnemonic(key_seed))
|
2022-03-16 21:47:54 +03:00
|
|
|
first_pk = keychain.get_first_public_key()
|
|
|
|
assert first_pk is not None
|
|
|
|
db_path_key_suffix = str(first_pk.get_fingerprint())
|
2022-05-23 18:13:49 +03:00
|
|
|
db_name = f"test-wallet-db-{full_node_port}-KEY.sqlite"
|
2022-03-16 21:47:54 +03:00
|
|
|
db_path_replaced: str = db_name.replace("KEY", db_path_key_suffix)
|
|
|
|
db_path = local_bt.root_path / db_path_replaced
|
|
|
|
|
|
|
|
if db_path.exists():
|
|
|
|
db_path.unlink()
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["database_path"] = str(db_name)
|
|
|
|
service_config["testing"] = True
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["introducer_peer"]["host"] = self_hostname
|
2022-03-16 21:47:54 +03:00
|
|
|
if introducer_port is not None:
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["introducer_peer"]["port"] = introducer_port
|
|
|
|
service_config["peer_connect_interval"] = 10
|
2022-03-16 21:47:54 +03:00
|
|
|
else:
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["introducer_peer"] = None
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
if full_node_port is not None:
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["full_node_peer"] = {}
|
|
|
|
service_config["full_node_peer"]["host"] = self_hostname
|
|
|
|
service_config["full_node_peer"]["port"] = full_node_port
|
2022-03-16 21:47:54 +03:00
|
|
|
else:
|
2022-07-08 03:57:08 +03:00
|
|
|
del service_config["full_node_peer"]
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-07-13 19:43:48 +03:00
|
|
|
service = create_wallet_service(
|
|
|
|
local_bt.root_path,
|
|
|
|
config,
|
|
|
|
consensus_constants,
|
|
|
|
keychain,
|
2022-03-16 21:47:54 +03:00
|
|
|
connect_to_daemon=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
await service.start()
|
|
|
|
|
2022-08-09 04:05:20 +03:00
|
|
|
# TODO, just always yield the service only and adjust all other places
|
|
|
|
if yield_service:
|
|
|
|
yield service
|
|
|
|
else:
|
|
|
|
yield service._node, service._node.server
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
service.stop()
|
|
|
|
await service.wait_closed()
|
|
|
|
if db_path.exists():
|
|
|
|
db_path.unlink()
|
|
|
|
keychain.delete_all_keys()
|
|
|
|
|
|
|
|
|
|
|
|
async def setup_harvester(
|
2022-05-03 05:32:37 +03:00
|
|
|
b_tools: BlockTools,
|
2022-04-08 03:10:44 +03:00
|
|
|
root_path: Path,
|
2022-08-10 12:57:34 +03:00
|
|
|
farmer_peer: Optional[PeerInfo],
|
2022-03-16 21:47:54 +03:00
|
|
|
consensus_constants: ConsensusConstants,
|
|
|
|
start_service: bool = True,
|
|
|
|
):
|
2022-05-03 05:32:37 +03:00
|
|
|
init(None, root_path)
|
|
|
|
init(b_tools.root_path / "config" / "ssl" / "ca", root_path)
|
2022-05-05 18:16:51 +03:00
|
|
|
with lock_and_load_config(root_path, "config.yaml") as config:
|
|
|
|
config["logging"]["log_stdout"] = True
|
|
|
|
config["selected_network"] = "testnet0"
|
|
|
|
config["harvester"]["selected_network"] = "testnet0"
|
2022-05-23 18:13:49 +03:00
|
|
|
config["harvester"]["port"] = 0
|
|
|
|
config["harvester"]["rpc_port"] = 0
|
2022-05-05 18:16:51 +03:00
|
|
|
config["harvester"]["plot_directories"] = [str(b_tools.plot_dir.resolve())]
|
|
|
|
save_config(root_path, "config.yaml", config)
|
2022-07-13 19:43:48 +03:00
|
|
|
service = create_harvester_service(
|
|
|
|
root_path,
|
|
|
|
config,
|
|
|
|
consensus_constants,
|
2022-08-10 12:57:34 +03:00
|
|
|
farmer_peer=farmer_peer,
|
2022-03-16 21:47:54 +03:00
|
|
|
connect_to_daemon=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
if start_service:
|
|
|
|
await service.start()
|
|
|
|
|
|
|
|
yield service
|
|
|
|
|
|
|
|
service.stop()
|
|
|
|
await service.wait_closed()
|
|
|
|
|
|
|
|
|
|
|
|
async def setup_farmer(
|
|
|
|
b_tools: BlockTools,
|
2022-05-03 05:32:37 +03:00
|
|
|
root_path: Path,
|
2022-03-16 21:47:54 +03:00
|
|
|
self_hostname: str,
|
|
|
|
consensus_constants: ConsensusConstants,
|
|
|
|
full_node_port: Optional[uint16] = None,
|
|
|
|
start_service: bool = True,
|
2022-05-23 18:13:49 +03:00
|
|
|
port: uint16 = uint16(0),
|
2022-03-16 21:47:54 +03:00
|
|
|
):
|
2022-05-03 05:32:37 +03:00
|
|
|
init(None, root_path)
|
|
|
|
init(b_tools.root_path / "config" / "ssl" / "ca", root_path)
|
2022-05-05 18:16:51 +03:00
|
|
|
with lock_and_load_config(root_path, "config.yaml") as root_config:
|
|
|
|
root_config["logging"]["log_stdout"] = True
|
|
|
|
root_config["selected_network"] = "testnet0"
|
|
|
|
root_config["farmer"]["selected_network"] = "testnet0"
|
|
|
|
save_config(root_path, "config.yaml", root_config)
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config = root_config["farmer"]
|
2022-05-03 05:32:37 +03:00
|
|
|
config_pool = root_config["pool"]
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["xch_target_address"] = encode_puzzle_hash(b_tools.farmer_ph, "xch")
|
|
|
|
service_config["pool_public_keys"] = [bytes(pk).hex() for pk in b_tools.pool_pubkeys]
|
|
|
|
service_config["port"] = port
|
|
|
|
service_config["rpc_port"] = uint16(0)
|
2022-03-16 21:47:54 +03:00
|
|
|
config_pool["xch_target_address"] = encode_puzzle_hash(b_tools.pool_ph, "xch")
|
|
|
|
|
|
|
|
if full_node_port:
|
2022-07-08 03:57:08 +03:00
|
|
|
service_config["full_node_peer"]["host"] = self_hostname
|
|
|
|
service_config["full_node_peer"]["port"] = full_node_port
|
2022-03-16 21:47:54 +03:00
|
|
|
else:
|
2022-07-08 03:57:08 +03:00
|
|
|
del service_config["full_node_peer"]
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-07-13 19:43:48 +03:00
|
|
|
service = create_farmer_service(
|
|
|
|
root_path,
|
|
|
|
root_config,
|
|
|
|
config_pool,
|
|
|
|
consensus_constants,
|
|
|
|
b_tools.local_keychain,
|
2022-03-16 21:47:54 +03:00
|
|
|
connect_to_daemon=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
if start_service:
|
|
|
|
await service.start()
|
|
|
|
|
|
|
|
yield service
|
|
|
|
|
|
|
|
service.stop()
|
|
|
|
await service.wait_closed()
|
|
|
|
|
|
|
|
|
2022-09-29 20:14:20 +03:00
|
|
|
async def setup_introducer(bt: BlockTools, port, yield_service: bool = False):
|
2022-07-13 19:43:48 +03:00
|
|
|
service = create_introducer_service(
|
2022-03-16 21:47:54 +03:00
|
|
|
bt.root_path,
|
2022-07-08 03:57:08 +03:00
|
|
|
bt.config,
|
2022-03-16 21:47:54 +03:00
|
|
|
advertised_port=port,
|
|
|
|
connect_to_daemon=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
await service.start()
|
|
|
|
|
2022-09-29 20:14:20 +03:00
|
|
|
if yield_service:
|
|
|
|
yield service
|
|
|
|
else:
|
|
|
|
yield service._api, service._node.server
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
service.stop()
|
|
|
|
await service.wait_closed()
|
|
|
|
|
|
|
|
|
|
|
|
async def setup_vdf_client(bt: BlockTools, self_hostname: str, port):
|
2022-08-03 10:28:32 +03:00
|
|
|
lock = asyncio.Lock()
|
|
|
|
vdf_task_1 = asyncio.create_task(spawn_process(self_hostname, port, 1, lock, bt.config.get("prefer_ipv6")))
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
def stop():
|
2022-08-03 10:28:32 +03:00
|
|
|
asyncio.create_task(kill_processes(lock))
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
asyncio.get_running_loop().add_signal_handler(signal.SIGTERM, stop)
|
|
|
|
asyncio.get_running_loop().add_signal_handler(signal.SIGINT, stop)
|
|
|
|
|
|
|
|
yield vdf_task_1
|
2022-08-03 10:28:32 +03:00
|
|
|
await kill_processes(lock)
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
async def setup_vdf_clients(bt: BlockTools, self_hostname: str, port):
|
2022-08-03 10:28:32 +03:00
|
|
|
lock = asyncio.Lock()
|
|
|
|
vdf_task_1 = asyncio.create_task(spawn_process(self_hostname, port, 1, lock, bt.config.get("prefer_ipv6")))
|
|
|
|
vdf_task_2 = asyncio.create_task(spawn_process(self_hostname, port, 2, lock, bt.config.get("prefer_ipv6")))
|
|
|
|
vdf_task_3 = asyncio.create_task(spawn_process(self_hostname, port, 3, lock, bt.config.get("prefer_ipv6")))
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
def stop():
|
2022-08-03 10:28:32 +03:00
|
|
|
asyncio.create_task(kill_processes(lock))
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
asyncio.get_running_loop().add_signal_handler(signal.SIGTERM, stop)
|
|
|
|
asyncio.get_running_loop().add_signal_handler(signal.SIGINT, stop)
|
|
|
|
|
|
|
|
yield vdf_task_1, vdf_task_2, vdf_task_3
|
|
|
|
|
2022-08-03 10:28:32 +03:00
|
|
|
await kill_processes(lock)
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
async def setup_timelord(
|
2022-05-23 18:13:49 +03:00
|
|
|
full_node_port,
|
|
|
|
sanitizer,
|
|
|
|
consensus_constants: ConsensusConstants,
|
|
|
|
b_tools: BlockTools,
|
|
|
|
vdf_port: uint16 = uint16(0),
|
2022-09-29 20:14:20 +03:00
|
|
|
yield_service: bool = False,
|
2022-03-16 21:47:54 +03:00
|
|
|
):
|
2022-07-08 03:57:08 +03:00
|
|
|
config = b_tools.config
|
|
|
|
service_config = config["timelord"]
|
|
|
|
service_config["full_node_peer"]["port"] = full_node_port
|
|
|
|
service_config["bluebox_mode"] = sanitizer
|
|
|
|
service_config["fast_algorithm"] = False
|
|
|
|
service_config["vdf_server"]["port"] = vdf_port
|
|
|
|
service_config["start_rpc_server"] = True
|
|
|
|
service_config["rpc_port"] = uint16(0)
|
2022-03-16 21:47:54 +03:00
|
|
|
|
2022-07-13 19:43:48 +03:00
|
|
|
service = create_timelord_service(
|
|
|
|
b_tools.root_path,
|
|
|
|
config,
|
|
|
|
consensus_constants,
|
2022-03-16 21:47:54 +03:00
|
|
|
connect_to_daemon=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
await service.start()
|
|
|
|
|
2022-09-29 20:14:20 +03:00
|
|
|
if yield_service:
|
|
|
|
yield service
|
|
|
|
else:
|
|
|
|
yield service._api, service._node.server
|
2022-03-16 21:47:54 +03:00
|
|
|
|
|
|
|
service.stop()
|
|
|
|
await service.wait_closed()
|