Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 0 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ RELAYER_PORT=8003
VALIDATORS_MANAGER_KEY_FILE=validators-manager-key.json
VALIDATORS_MANAGER_PASSWORD_FILE=validators-manager-password.txt

# keystore
KEYSTORES_DIR=/keystores
KEYSTORES_PASSWORD_DIR=/keystores
KEYSTORES_PASSWORD_FILE=/keystores/password.txt

# choices: mainnet, hoodi, gnosis, chiado
NETWORK=

Expand Down
5 changes: 0 additions & 5 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from src.common.setup_logging import setup_logging
from src.config import settings
from src.validators.endpoints import router
from src.validators.keystore import LocalKeystore
from src.validators.validators_manager import load_validators_manager_account

setup_logging()
Expand All @@ -27,10 +26,6 @@ async def lifespan(app_instance: FastAPI) -> AsyncIterator: # pylint:disable=un
app_state.validators_manager_account = validators_manager
logger.info('validators manager address: %s', validators_manager.address)

# load keystore
keystore = await LocalKeystore.load()
app_state.keystore = keystore

yield


Expand Down
2 changes: 0 additions & 2 deletions src/common/app_state.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from eth_account.signers.local import LocalAccount

from src.common.typings import Singleton
from src.validators.keystore import LocalKeystore


class AppState(metaclass=Singleton):
validators_manager_account: LocalAccount
keystore: LocalKeystore
17 changes: 0 additions & 17 deletions src/config/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from pathlib import Path

from decouple import config

from src.config.networks import NETWORKS
Expand All @@ -10,28 +8,13 @@
validators_manager_key_file: str = config('VALIDATORS_MANAGER_KEY_FILE')
validators_manager_password_file: str = config('VALIDATORS_MANAGER_PASSWORD_FILE')

keystores_dir = config(
'KEYSTORES_DIR',
cast=Path,
)
keystores_password_dir = config(
'KEYSTORES_PASSWORD_DIR',
cast=Path,
)
keystores_password_file = config(
'KEYSTORES_PASSWORD_FILE',
cast=Path,
)

network: str = config('NETWORK')
network_config = NETWORKS[network]

execution_endpoint: str = config('EXECUTION_ENDPOINT')
execution_timeout: int = config('EXECUTION_TIMEOUT', cast=int, default=60)
execution_retry_timeout: int = config('EXECUTION_RETRY_TIMEOUT', cast=int, default=60)

concurrency: int = config('CONCURRENCY', cast=int, default=1)

# logging
LOG_PLAIN = 'plain'
LOG_JSON = 'json'
Expand Down
54 changes: 54 additions & 0 deletions src/validators/credentials.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import secrets
from dataclasses import dataclass
from functools import cached_property

import milagro_bls_binding as bls
from eth_typing import BLSPrivateKey, ChecksumAddress, HexStr
from py_ecc.bls import G2ProofOfPossession
from py_ecc.optimized_bls12_381.optimized_curve import curve_order
from staking_deposit.key_handling.key_derivation.path import path_to_nodes
from staking_deposit.key_handling.key_derivation.tree import derive_child_SK
from staking_deposit.settings import DEPOSIT_CLI_VERSION
from sw_utils import get_v1_withdrawal_credentials, get_v2_withdrawal_credentials
from sw_utils.signing import (
Expand All @@ -18,6 +22,11 @@
from src.config.networks import NETWORKS
from src.validators.typings import ValidatorType

# Set path as EIP-2334 format
# https://eips.ethereum.org/EIPS/eip-2334
PURPOSE = '12381'
COIN_TYPE = '3600'


@dataclass
class Credential:
Expand Down Expand Up @@ -75,3 +84,48 @@ def get_signed_deposit(self, amount: int) -> DepositData:
signature=bls.Sign(self.private_key_bytes, signing_root),
)
return signed_deposit


class CredentialManager:
@staticmethod
def generate_credentials(
count: int,
start_index: int,
network: str,
vault_address: ChecksumAddress,
validator_type: ValidatorType,
) -> list[Credential]:
credentials = []
private_key = BLSPrivateKey(secrets.randbelow(curve_order))
for index in range(start_index, start_index + count):
credential = CredentialManager._generate_credential(
network=network,
vault=vault_address,
private_key=private_key,
index=index,
validator_type=validator_type,
)
credentials.append(credential)
return credentials

@staticmethod
def _generate_credential(
network: str,
vault: ChecksumAddress,
private_key: BLSPrivateKey,
index: int,
validator_type: ValidatorType,
) -> Credential:
signing_key_path = f'm/{PURPOSE}/{COIN_TYPE}/{index}/0/0'
nodes = path_to_nodes(signing_key_path)

for node in nodes:
private_key = BLSPrivateKey(derive_child_SK(parent_SK=private_key, index=node))

return Credential(
private_key=private_key,
path=signing_key_path,
network=network,
vault=vault,
validator_type=validator_type,
)
50 changes: 23 additions & 27 deletions src/validators/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from fastapi import APIRouter
from sw_utils import DepositData, get_v2_withdrawal_credentials
from web3 import Web3

from src.common.app_state import AppState
from src.common.contracts import VaultContract, validators_registry_contract
from src.validators import schema
from src.validators.validators import generate_validators, get_validators_for_funding
from src.validators.typings import Validator
from src.validators.validators import generate_validators
from src.validators.validators_manager import (
get_validators_manager_signature_consolidation,
get_validators_manager_signature_funding,
Expand All @@ -15,14 +16,12 @@
router = APIRouter()


@router.post('/validators')
@router.post('/register')
async def register_validators(
request: schema.ValidatorsRegisterRequest,
) -> schema.ValidatorsRegisterResponse:
validator_items = []
app_state = AppState()
validators = generate_validators(
keystore=app_state.keystore,
vault_address=request.vault,
start_index=request.validators_start_index,
amounts=request.amounts,
Expand Down Expand Up @@ -56,27 +55,25 @@ async def register_validators(
@router.post('/fund')
async def fund_validators(
request: schema.ValidatorsFundRequest,
) -> schema.ValidatorsFundResponse:
validator_items = []
app_state = AppState()
if not app_state.keystore:
raise ValueError('Keystore is required for funding validators')

validators = get_validators_for_funding(
keystore=app_state.keystore,
vault_address=request.vault,
public_keys=request.public_keys,
amounts=request.amounts,
)

for validator in validators:
validator_items.append(
schema.ValidatorsFundResponseItem(
public_key=validator.public_key,
deposit_signature=validator.deposit_signature,
amount=validator.amount,
)
) -> schema.ValidatorsSignatureResponse:
validators = []

# use empty signature for funding
empty_signature = bytes(96)
for public_key, amount in zip(request.public_keys, request.amounts):
deposit_data = DepositData(
pubkey=Web3.to_bytes(hexstr=public_key),
withdrawal_credentials=get_v2_withdrawal_credentials(request.vault),
amount=amount,
signature=empty_signature,
)
validator = Validator(
public_key=public_key,
amount=amount,
deposit_signature=Web3.to_hex(empty_signature),
deposit_data_root=Web3.to_hex(deposit_data.hash_tree_root),
)
validators.append(validator)

vault_contact = VaultContract(request.vault)
validators_manager_nonce = await vault_contact.validators_manager_nonce()
Expand All @@ -86,8 +83,7 @@ async def fund_validators(
validators,
)

return schema.ValidatorsFundResponse(
validators=validator_items,
return schema.ValidatorsSignatureResponse(
validators_manager_signature=validators_manager_signature,
)

Expand Down
Loading
Loading