Skip to content

Commit

Permalink
Add start-api command
Browse files Browse the repository at this point in the history
  • Loading branch information
evgeny-stakewise committed Feb 12, 2024
1 parent d7349e2 commit 1087d1b
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 159 deletions.
162 changes: 3 additions & 159 deletions src/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,22 @@
from pathlib import Path

import click
import uvicorn
from eth_typing import ChecksumAddress
from sw_utils import EventScanner, InterruptHandler

import src
import src.validators.api.endpoints # noqa
from src.api import app as api_app
from src.common.consensus import get_chain_finalized_head
from src.common.execution import WalletTask, get_oracles
from src.common.logging import LOG_LEVELS, setup_logging
from src.common.metrics import MetricsTask, metrics_server
from src.common.startup_check import startup_checks
from src.common.utils import get_build_version, log_verbose
from src.commands.start_base import main
from src.common.logging import LOG_LEVELS
from src.common.utils import log_verbose
from src.common.validators import validate_eth_address
from src.common.vault_config import VaultConfig
from src.config.settings import (
AVAILABLE_NETWORKS,
DEFAULT_API_HOST,
DEFAULT_API_PORT,
DEFAULT_MAX_FEE_PER_GAS_GWEI,
DEFAULT_METRICS_HOST,
DEFAULT_METRICS_PORT,
LOG_FORMATS,
LOG_PLAIN,
settings,
)
from src.exits.tasks import ExitSignatureTask
from src.harvest.tasks import HarvestTask
from src.validators.database import NetworkValidatorCrud
from src.validators.execution import NetworkValidatorsProcessor
from src.validators.keystores.load import load_keystore
from src.validators.tasks import ValidatorsTask, load_genesis_validators
from src.validators.typings import ValidatorsRegistrationMode
from src.validators.utils import load_deposit_data

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -77,13 +59,6 @@
help='Absolute path to the hot wallet. '
'Default is the file generated with "create-wallet" command.',
)
@click.option(
'--no-keystores',
is_flag=True,
envvar='NO_KEYSTORES',
help='If this option is enabled then exit signatures are generated by another service. '
'Disabled by default.',
)
@click.option(
'--keystores-password-file',
type=click.Path(exists=True, file_okay=True, dir_okay=False),
Expand Down Expand Up @@ -216,34 +191,6 @@
envvar='POOL_SIZE',
type=int,
)
@click.option(
'--enable-api',
is_flag=True,
envvar='ENABLE_API',
help='Whether to enable API server. Disabled by default.',
)
@click.option(
'--api-host',
type=str,
help=f'API host. Default is {DEFAULT_API_HOST}.',
envvar='API_HOST',
default=DEFAULT_API_HOST,
)
@click.option(
'--api-port',
type=int,
help=f'API port. Default is {DEFAULT_API_PORT}.',
envvar='API_PORT',
default=DEFAULT_API_PORT,
)
@click.option(
'--validators-registration-mode',
type=click.Choice(['AUTO', 'API'], case_sensitive=False),
default='AUTO',
envvar='VALIDATORS_REGISTRATION_MODE',
help='AUTO mode: validators are registered automatically when vault assets are enough.\n'
'API mode: validators registration is triggered by API request.',
)
@click.command(help='Start operator service')
# pylint: disable-next=too-many-arguments,too-many-locals
def start(
Expand All @@ -260,7 +207,6 @@ def start(
log_format: str,
network: str | None,
deposit_data_file: str | None,
no_keystores: bool,
keystores_dir: str | None,
keystores_password_file: str | None,
remote_signer_url: str | None,
Expand All @@ -272,10 +218,6 @@ def start(
max_fee_per_gas_gwei: int,
database_dir: str | None,
pool_size: int | None,
enable_api: bool,
api_host: str,
api_port: int,
validators_registration_mode: str,
) -> None:
vault_config = VaultConfig(vault, Path(data_dir))
if network is None:
Expand All @@ -294,7 +236,6 @@ def start(
metrics_port=metrics_port,
network=network,
deposit_data_file=deposit_data_file,
no_keystores=no_keystores,
keystores_dir=keystores_dir,
keystores_password_file=keystores_password_file,
remote_signer_url=remote_signer_url,
Expand All @@ -308,106 +249,9 @@ def start(
log_level=log_level,
log_format=log_format,
pool_size=pool_size,
enable_api=enable_api,
api_host=api_host,
api_port=api_port,
validators_registration_mode=ValidatorsRegistrationMode[validators_registration_mode],
)

try:
asyncio.run(main())
except Exception as e:
log_verbose(e)


async def main() -> None:
setup_logging()
setup_sentry()
log_start()

if not settings.skip_startup_checks:
await startup_checks()

NetworkValidatorCrud().setup()

# load network validators from ipfs dump
await load_genesis_validators()

# load keystore
keystore = await load_keystore()

# load deposit data
deposit_data = load_deposit_data(settings.vault, settings.deposit_data_file)
logger.info('Loaded deposit data file %s', settings.deposit_data_file)
# start operator tasks

# periodically scan network validator updates
network_validators_processor = NetworkValidatorsProcessor()
network_validators_scanner = EventScanner(network_validators_processor)

logger.info('Syncing network validator events...')
chain_state = await get_chain_finalized_head()
await network_validators_scanner.process_new_events(chain_state.execution_block)

logger.info('Warming up oracles cache...')
await get_oracles()

if settings.enable_api:
logger.info('Starting api server')
api_app.state.deposit_data = deposit_data

config = uvicorn.Config(
api_app,
host=settings.api_host,
port=settings.api_port,
log_config=None,
)
server = UvicornServerWithoutSignals(config)
asyncio.create_task(server.serve())

if settings.enable_metrics:
await metrics_server()

logger.info('Started operator service')
with InterruptHandler() as interrupt_handler:
tasks = [
ValidatorsTask(
keystore=keystore,
deposit_data=deposit_data,
).run(interrupt_handler),
ExitSignatureTask(
keystore=keystore,
).run(interrupt_handler),
MetricsTask().run(interrupt_handler),
WalletTask().run(interrupt_handler),
]
if settings.harvest_vault:
tasks.append(HarvestTask().run(interrupt_handler))

await asyncio.gather(*tasks)


class UvicornServerWithoutSignals(uvicorn.Server):
def install_signal_handlers(self) -> None:
# Manage signals in command, not in Uvicorn
pass


def log_start() -> None:
build = get_build_version()
start_str = 'Starting operator service'

if build:
logger.info('%s, version %s, build %s', start_str, src.__version__, build)
else:
logger.info('%s, version %s', start_str, src.__version__)


def setup_sentry():
if settings.sentry_dsn:
# pylint: disable-next=import-outside-toplevel
import sentry_sdk

sentry_sdk.init(settings.sentry_dsn, traces_sample_rate=0.1)
sentry_sdk.set_tag('network', settings.network)
sentry_sdk.set_tag('vault', settings.vault)
Loading

0 comments on commit 1087d1b

Please sign in to comment.