Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ace4e63
Add relayer skeleton (#17)
evgeny-stakewise Feb 18, 2026
20a8fde
Remove network validators (#18)
evgeny-stakewise Feb 18, 2026
ed7fef4
Merged registration endpoint (#19)
evgeny-stakewise Feb 18, 2026
882f0ab
Add public keys file (#20)
evgeny-stakewise Feb 19, 2026
6364243
Add deposit signature (#21)
evgeny-stakewise Feb 20, 2026
27404d8
Track registered keys (#22)
evgeny-stakewise Feb 23, 2026
8d47566
Cleanup tests (#23)
evgeny-stakewise Feb 23, 2026
9a0716d
Bump v1.0.0.dev1
evgeny-stakewise Feb 24, 2026
9f90440
Updated settings and readme (#24)
evgeny-stakewise Feb 24, 2026
bf32ad2
Speed up getting last event (#25)
evgeny-stakewise Feb 25, 2026
42b6942
Add validators manager address to /info endpoint (#26)
evgeny-stakewise Feb 27, 2026
4319a02
Add TestRegisterSignatureAggregation
evgeny-stakewise Mar 6, 2026
c36c767
Use test client
evgeny-stakewise Mar 6, 2026
243c744
Add test register for 2 validators
evgeny-stakewise Mar 6, 2026
02de355
Bump version v1.0.0
evgeny-stakewise Mar 6, 2026
cffe6f0
Add note on test exit signatures
evgeny-stakewise Mar 6, 2026
3cf284a
Add patch_vault_contract
evgeny-stakewise Mar 6, 2026
ddec9c7
Mock protocol config
evgeny-stakewise Mar 8, 2026
85cc25c
Skip signature reconstruction when already completed
evgeny-stakewise Mar 9, 2026
2293825
Add ChecksumAddressField, return 400 on invalid signatures, recreate …
evgeny-stakewise Mar 12, 2026
a87e56f
Normalize BLS public keys to canonical lowercase hex
evgeny-stakewise Mar 12, 2026
84e767f
Validate non-empty amounts in request schemas
evgeny-stakewise Mar 16, 2026
7036e1e
Upd sw-utils to v0.12.12
evgeny-stakewise Mar 16, 2026
9d62404
Replace CSV format with plain text for public keys file
evgeny-stakewise Mar 16, 2026
1acfa07
Fix lint
evgeny-stakewise Mar 16, 2026
cbd8867
Fix lint
evgeny-stakewise Mar 16, 2026
32dee56
Add public_keys.txt to gitignore
evgeny-stakewise Mar 16, 2026
ce1d21f
Add 0x to validators manager signature
evgeny-stakewise Mar 16, 2026
5a7e8f9
Add comment
evgeny-stakewise Mar 18, 2026
80d680f
Save signature shares even if signature is ready
evgeny-stakewise Mar 18, 2026
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
9 changes: 8 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ NETWORK=hoodi
EXECUTION_ENDPOINT=https://hoodi-geth
CONSENSUS_ENDPOINT=https://hoodi-lighthouse

DATABASE=relayer.db
PUBLIC_KEYS_FILE=public_keys.txt

# Validators manager
VALIDATORS_MANAGER_KEY_FILE=validators-manager-key.json
VALIDATORS_MANAGER_PASSWORD_FILE=validators-manager-password.txt

# Maximum number of parallel event scan queries
# EVENT_LOGS_MAX_CONCURRENCY=4
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ jobs:
- name: Install dependencies
run: poetry install --no-interaction

- name: Create env-file
run: cp .env.example .env

# Run tests
- name: Run tests
run: poetry run coverage run -m pytest src
Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,13 @@ cython_debug/

# Sqlite db
*.db

# Claude
.claude
CLAUDE.md

# request files
*.http

# Public keys
public_keys.txt
44 changes: 40 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,48 @@ In production environment:
3. `cp .env.example .env`
4. Fill .env file with appropriate values

## Run
## Run from sources

1. `poetry shell`
2. `export PYTHONPATH=.`
3. `python src/app.py`

## Run with Docker

```bash
export DVT_RELAYER_VERSION=v1.0.0
```

Pull the image:

```bash
docker pull europe-west4-docker.pkg.dev/stakewiselabs/public/dvt-relayer:$DVT_RELAYER_VERSION
```

You can also build the image from source:

```bash
docker build --pull -t europe-west4-docker.pkg.dev/stakewiselabs/public/dvt-relayer:$DVT_RELAYER_VERSION .
```

Run the container, mounting a directory with your `.env` and key files:

```bash
docker run --rm -ti \
--env-file /path/to/.env \
-v /path/to/data:/data \
-p 8000:8000 \
europe-west4-docker.pkg.dev/stakewiselabs/public/dvt-relayer:$DVT_RELAYER_VERSION
```

Set paths in `.env` to point inside the container, e.g.:

```ini
VALIDATORS_MANAGER_KEY_FILE=/data/validators-manager-key.json
VALIDATORS_MANAGER_PASSWORD_FILE=/data/validators-manager-password.txt
PUBLIC_KEYS_FILE=/data/public_keys.txt
```

## Test

Running the whole cluster of DVT sidecars locally may be cumbersome.
Expand All @@ -43,6 +79,6 @@ See [DVT sidecar readme](https://github.com/stakewise/dvt-operator-sidecar/blob/

DVT sidecar:

1. Loads DV keystores
2. Polls validator exits from Relayer
3. Pushes exit signature shares to Relayer on behalf of DVT operators.
1. Loads distributed validator (DV) keystores.
2. Retrieves validator data from the Relayer.
3. Submits deposit signature shares and exit signature shares to the Relayer on behalf of DVT operators.
1,315 changes: 662 additions & 653 deletions poetry.lock

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "dvt-relayer"
version = "v0.3.1"
version = "v1.0.0"
description = "StakeWise operator relayer service for DVT validator setup"
authors = ["StakeWise Labs <info@stakewise.io>"]
package-mode = false
Expand All @@ -9,7 +9,7 @@ package-mode = false
python = "^3.12"
python-decouple = "==3.8"
sentry-sdk = "==1.45.1"
sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.12.9"}
sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.12.12"}
click = "==8.1.7"
tomli = "~2"
prometheus-client = "==0.17.1"
Expand Down Expand Up @@ -38,6 +38,7 @@ coverage = "==7.3.2"
aioresponses = "^0.7.4"
types-requests = "^2.31.0"
types-setuptools = "^70.0.0"
httpx = "^0.28.1"

[build-system]
requires = ["poetry-core"]
Expand Down Expand Up @@ -107,6 +108,9 @@ exclude = '''
)/
'''

[tool.pytest.ini_options]
asyncio_mode = "auto"

[tool.coverage.report]
fail_under = 73

Expand Down
27 changes: 16 additions & 11 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,17 @@
from starlette.requests import Request

from src.app_state import AppState
from src.common.consensus import get_chain_finalized_head
from src.common.endpoints import router as common_router
from src.common.setup_logging import setup_logging, setup_sentry
from src.common.utils import get_project_version
from src.config import settings
from src.protocol_config.tasks import ProtocolConfigTask, update_protocol_config
from src.validators.database import NetworkValidatorCrud
from src.relayer.endpoints import router as relayer_router
from src.relayer.public_keys import PublicKeysManager
from src.relayer.validators_manager import load_validators_manager_account
from src.validators.endpoints import router as validators_router
from src.validators.tasks import (
CleanupValidatorsTask,
NetworkValidatorsTask,
load_genesis_validators,
)
from src.validators.tasks import CleanupValidatorsTask, NetworkValidatorsTask

setup_logging()
logger = logging.getLogger(__name__)
Expand All @@ -36,25 +35,30 @@ async def lifespan(app_instance: FastAPI) -> AsyncIterator:

app_state = AppState()

app_state.validators = {}
# load validators manager account
validators_manager = load_validators_manager_account()
app_state.validators_manager_account = validators_manager
logger.info('validators manager address: %s', validators_manager.address)

NetworkValidatorCrud().setup()
await load_genesis_validators()
chain_head = await get_chain_finalized_head()
app_state.public_keys_manager = await PublicKeysManager.build(chain_head)

app_state.validators = {}

logger.info('Fetching protocol config...')
await update_protocol_config()
logger.info('Protocol config is ready')

# Note: we create a strong references to the tasks. Helps to avoid garbage collecting.
protocol_config_task = asyncio.create_task(ProtocolConfigTask().run())
network_validators_task = asyncio.create_task(NetworkValidatorsTask().run())
cleanup_validators_task = asyncio.create_task(CleanupValidatorsTask().run())
network_validators_task = asyncio.create_task(NetworkValidatorsTask().run())

yield

protocol_config_task.cancel()
network_validators_task.cancel()
cleanup_validators_task.cancel()
network_validators_task.cancel()


app = FastAPI(lifespan=lifespan)
Expand All @@ -79,6 +83,7 @@ async def log_request_processing_time(request: Request, call_next: Callable) ->
logger.info('Request processing time for path %s is %.1f', request.url.path, elapsed)


app.include_router(relayer_router)
app.include_router(validators_router)
app.include_router(common_router)

Expand Down
7 changes: 6 additions & 1 deletion src/app_state.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from eth_account.signers.local import LocalAccount
from eth_typing import HexStr
from sw_utils import ProtocolConfig

from src.common.typings import OraclesCache, Singleton
from src.validators.typings import Validator
from src.relayer.public_keys import PublicKeysManager
from src.relayer.typings import Validator


class AppState(metaclass=Singleton):
oracles_cache: OraclesCache | None = None
protocol_config: ProtocolConfig

public_keys_manager: PublicKeysManager
validators: dict[HexStr, Validator]
validators_manager_account: LocalAccount
Loading
Loading