Skip to content

Commit

Permalink
Add api for obol
Browse files Browse the repository at this point in the history
  • Loading branch information
evgeny-stakewise committed Jan 23, 2024
1 parent cd8d0be commit 42cbc81
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 6 deletions.
82 changes: 81 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ psycopg2 = "==2.9.9"
pyyaml = "==6.0.1"
aiohttp = "==3.9.1"
python-json-logger = "==2.0.7"
starlette = "==0.36.1"
uvicorn = "==0.27.0"

[tool.poetry.group.dev.dependencies]
pylint = "==3.0.1"
Expand Down
11 changes: 11 additions & 0 deletions src/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from starlette.applications import Starlette
from starlette.routing import Route

from src.validators.endpoints import approve_validators, get_validators

app = Starlette(
routes=[
Route('/validators', get_validators, methods=['GET']),
Route('/validators', approve_validators, methods=['POST']),
]
)
44 changes: 43 additions & 1 deletion src/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
from pathlib import Path

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

import src
import src.validators.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
from src.common.metrics import MetricsTask, metrics_server
Expand All @@ -17,6 +20,7 @@
from src.common.vault_config import VaultConfig
from src.config.settings import (
AVAILABLE_NETWORKS,
DEFAULT_API_PORT,
DEFAULT_MAX_FEE_PER_GAS_GWEI,
DEFAULT_METRICS_HOST,
DEFAULT_METRICS_PORT,
Expand Down Expand Up @@ -205,6 +209,26 @@
envvar='LOG_LEVEL',
help='The log level.',
)
@click.option(
'--enable-api',
is_flag=True,
envvar='ENABLE_API',
help='Whether to enable API server. Disabled by default.',
)
@click.option(
'--api-port',
type=int,
help=f'API port. Default is {DEFAULT_API_PORT}.',
envvar='API_PORT',
default=DEFAULT_API_PORT,
)
@click.option(
'--enable-validators-task',
is_flag=True,
default=True,
envvar='ENABLE_VALIDATORS_TASK',
help='Whether to enable validators task. Enabled by default.',
)
@click.command(help='Start operator service')
# pylint: disable-next=too-many-arguments,too-many-locals
def start(
Expand All @@ -231,6 +255,9 @@ def start(
hot_wallet_password_file: str | None,
max_fee_per_gas_gwei: int,
database_dir: str | None,
enable_api: bool,
api_port: int,
enable_validators_task: bool,
) -> None:
vault_config = VaultConfig(vault, Path(data_dir))
if network is None:
Expand Down Expand Up @@ -261,6 +288,9 @@ def start(
database_dir=database_dir,
log_level=log_level,
log_format=log_format,
enable_api=enable_api,
api_port=api_port,
enable_validators_task=enable_validators_task,
)

try:
Expand All @@ -274,7 +304,8 @@ async def main() -> None:
setup_sentry()
log_start()

await startup_checks()
# todo undo
# await startup_checks()

NetworkValidatorCrud().setup()

Expand All @@ -297,6 +328,17 @@ async def main() -> None:
chain_state = await get_chain_finalized_head()
await network_validators_scanner.process_new_events(chain_state.execution_block)

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

config = uvicorn.Config(
api_app, port=settings.api_port, log_level=settings.log_level.lower()
)
server = uvicorn.Server(config)
asyncio.create_task(server.serve())

if settings.enable_metrics:
await metrics_server()

Expand Down
5 changes: 5 additions & 0 deletions src/common/metrics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from prometheus_client import Gauge, Info, start_http_server

import src
Expand Down Expand Up @@ -27,8 +29,11 @@ def set_app_version(self):
metrics = Metrics()
metrics.set_app_version()

logger = logging.getLogger(__name__)


async def metrics_server() -> None:
logger.info('Starting metrics server')
start_http_server(settings.metrics_port, settings.metrics_host)


Expand Down
11 changes: 11 additions & 0 deletions src/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
DEFAULT_MAX_FEE_PER_GAS_GWEI = 100
DEFAULT_METRICS_HOST = '127.0.0.1'
DEFAULT_METRICS_PORT = 9100
DEFAULT_API_PORT = 5000


class Singleton(type):
Expand Down Expand Up @@ -64,6 +65,10 @@ class Settings(metaclass=Singleton):
sentry_dsn: str
pool_size: int | None

enable_api: bool
api_port: int
enable_validators_task: bool

# pylint: disable-next=too-many-arguments,too-many-locals
def set(
self,
Expand Down Expand Up @@ -91,6 +96,9 @@ def set(
database_dir: str | None = None,
log_level: str | None = None,
log_format: str | None = None,
enable_api: bool = False,
api_port: int = DEFAULT_API_PORT,
enable_validators_task: bool = True,
) -> None:
self.vault = Web3.to_checksum_address(vault)
vault_dir.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -185,6 +193,9 @@ def set(
self.consensus_retry_timeout = decouple_config(
'CONSENSUS_RETRY_TIMEOUT', default=120, cast=int
)
self.enable_api = enable_api
self.api_port = api_port
self.enable_validators_task = enable_validators_task

@property
def network_config(self) -> NetworkConfig:
Expand Down
69 changes: 69 additions & 0 deletions src/validators/endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import json

from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.status import HTTP_400_BAD_REQUEST
from web3.types import Wei

from src.common.execution import get_oracles
from src.common.utils import MGNO_RATE, WAD
from src.config.networks import GNOSIS
from src.config.settings import DEPOSIT_AMOUNT, settings
from src.validators.database import NetworkValidatorCrud
from src.validators.execution import (
get_available_validators,
get_latest_network_validator_public_keys,
get_withdrawable_assets,
)
from src.validators.typings import Validator


async def get_validators(request: Request) -> Response:
vault_balance, _ = await get_withdrawable_assets()
if settings.network == GNOSIS:
# apply GNO -> mGNO exchange rate
vault_balance = Wei(int(vault_balance * MGNO_RATE // WAD))

# calculate number of validators that can be registered
validators_count = vault_balance // DEPOSIT_AMOUNT
if not validators_count:
# not enough balance to register validators
return JSONResponse([])

# get latest oracles
oracles = await get_oracles()

validators_count = min(oracles.validators_approval_batch_limit, validators_count)

validators: list[Validator] = await get_available_validators(
keystore=request.app.state.keystore,
deposit_data=request.app.state.deposit_data,
count=validators_count,
)
if not validators:
# All validators from `deposit_data` are already registered
return JSONResponse([])

# get next validator index for exit signature
latest_public_keys = await get_latest_network_validator_public_keys()
next_validator_index = NetworkValidatorCrud().get_next_validator_index(list(latest_public_keys))

return JSONResponse(
[
{'public_key': validator.public_key, 'index': index}
for index, validator in enumerate(validators, next_validator_index)
]
)


def approve_validators(request: Request) -> Response:
# pylint: disable=unused-argument
try:
payload = await request.json()
except json.JSONDecodeError as exc:
raise HTTPException(
status_code=HTTP_400_BAD_REQUEST, detail='invalid_request_body'
) from exc

return JSONResponse([])
9 changes: 5 additions & 4 deletions src/validators/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ async def process_block(self) -> None:
keystore=self.keystore,
deposit_data=self.deposit_data,
)
await register_validators(
keystore=self.keystore,
deposit_data=self.deposit_data,
)
# todo
# await register_validators(
# keystore=self.keystore,
# deposit_data=self.deposit_data,
# )


# pylint: disable-next=too-many-locals,too-many-branches
Expand Down

0 comments on commit 42cbc81

Please sign in to comment.