Skip to content

Add support for operator v4#27

Open
evgeny-stakewise wants to merge 21 commits intomainfrom
operator-v4
Open

Add support for operator v4#27
evgeny-stakewise wants to merge 21 commits intomainfrom
operator-v4

Conversation

@evgeny-stakewise
Copy link
Contributor

No description provided.

evgeny-stakewise and others added 17 commits February 18, 2026 11:17
* Add relayer skeleton

* Fix lint
* Refactor network validators

* Del network validators
* Refactor network validators

* Del network validators

* Merged validator types

* Merged registration endpoints
* Add deposit signature shares

* Add validator_type to validators response

* Add public keys file

* Rename endpoints, add is_deposit_signature_ready

* Rework deposit signature from HexStr to BLSSignature

* Review fixes
* Track registered public keys

* Handle pending deposits, refactor

* Exclude raw exit signature from response

* Fix env file in CI

* Add VALIDATORS_MANAGER_KEY_FILE to env example
* Remove unused settings

* Updated README

* Fix markdown

* Upd gitignore
* Speed up getting last event

* Improvements

* Review fix
* Add validators manager address to /info endpoint

* Fix lint
Copilot AI review requested due to automatic review settings March 6, 2026 20:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the service to support “operator v4” flows by introducing a relayer API for validator registration/funding/withdrawal/consolidation (with validators-manager EIP-712 signing), extending the sidecar → relayer signature-share submission to include deposit + exit signatures, and replacing the old DB/genesis-validator bootstrap with a file-driven public-keys manager tracked in-memory.

Changes:

  • Add new Relayer module (schemas, endpoints, typings, validators-manager signer) and related tests/fixtures.
  • Update Validators API to accept combined deposit/exit signature shares and expose richer validator metadata to sidecars.
  • Remove sqlite-backed network validators persistence + genesis IPFS bootstrap; use a CSV public-keys file and event scanning to track registrations.

Reviewed changes

Copilot reviewed 31 out of 36 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/validators/typings.py Removes old validator/network-validator dataclasses; keeps oracle shares dataclass.
src/validators/tasks.py Drops genesis validator loader; updates cleanup lifetime setting; scanner now uses AppState manager.
src/validators/schema.py Renames/reshapes request/response models for v4 (deposit+exit shares; richer validators response).
src/validators/exit_signature.py Adds deposit signature validation helper using sw_utils.
src/validators/execution.py Reworks network validator event processing to update PublicKeysManager instead of DB.
src/validators/endpoints.py Replaces old endpoints with GET /validators and POST /signatures for deposit+exit shares.
src/validators/database.py Deletes sqlite CRUD for network validators.
src/relayer/validators_manager.py New EIP-712 signing utilities for validators-manager signatures.
src/relayer/typings.py New Validator dataclass + ValidatorType supporting v1/v2 withdrawal credentials & deposit data root.
src/relayer/public_keys.py New CSV-backed public key loader and registration tracking (consensus + pending deposits + execution logs).
src/relayer/schema.py New request/response schemas for /register, /fund, /withdraw, /consolidate.
src/relayer/endpoints.py New relayer API endpoints that create validators and produce validators-manager signatures.
src/relayer/tests/test_public_keys.py Unit tests for PublicKeysManager behavior.
src/relayer/tests/test_endpoints.py Endpoint-flow tests, including signature-share aggregation.
src/relayer/tests/conftest.py Loads sidecar share fixtures for tests.
src/relayer/tests/fixtures/sidecar_shares_1_validator.json Test fixture for 1-validator signature shares.
src/relayer/tests/fixtures/sidecar_shares_2_validators.json Test fixture for 2-validator signature shares.
src/conftest.py Adds shared httpx test client fixture for FastAPI app.
src/config/settings.py Removes DB/genesis IPFS settings; adds validators-manager/public-keys settings + event concurrency config.
src/common/schema.py Extends /info response with validators-manager address.
src/common/endpoints.py Implements /info validators-manager address output.
src/common/contracts.py Adds concurrency to event scanning and adds Vault/Registry helper methods.
src/common/clients.py Removes sqlite DB client; keeps consensus/execution/ipfs clients.
src/common/abi/IEthVault.json Adds EthVault ABI for VaultContract wrapper.
src/common/tests/test_endpoints.py Adds test coverage for updated /info.
src/app_state.py Adds public_keys_manager + validators_manager_account to global state; switches validator type source.
src/app.py Loads validators-manager account + public keys on startup; registers relayer router; adjusts task startup.
pyproject.toml Bumps version to v1.0.0, adds httpx, configures pytest asyncio mode.
poetry.lock Locks new httpx/httpcore dependencies.
README.md Updates run instructions and sidecar interaction description for v4.
.env.example Replaces DB setting with keyfile/password/public-keys settings and concurrency option.
.github/workflows/ci.yaml Ensures .env exists in CI by copying .env.example.
.gitignore Ignores additional local/dev artifacts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings March 9, 2026 12:02
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 37 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 39 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +73
# Build all chunk ranges from newest to oldest
ranges: list[tuple[BlockNumber, BlockNumber]] = []
chunk_to = to_block
while chunk_to >= from_block:
chunk_from = BlockNumber(max(chunk_to - blocks_range + 1, from_block))
ranges.append((chunk_from, chunk_to))
chunk_to = BlockNumber(chunk_to - blocks_range)

# from_block and to_block are both inclusive
async def fetch_chunk(chunk_from: BlockNumber, chunk_to: BlockNumber) -> list[EventData]:
return await event.get_logs(
from_block=chunk_from,
to_block=chunk_to,
argument_filters=argument_filters,
)
if events:
return events[-1]
to_block = BlockNumber(to_block - blocks_range - 1)

# Process chunks in batches (newest-first), abort on first hit
for batch in itertools.batched(ranges, settings.event_logs_max_concurrency):
batch_results = await asyncio.gather(*[fetch_chunk(f, t) for f, t in batch])
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_last_event now builds a full ranges list for the entire [from_block,to_block] span before processing. For very large ranges this can be a substantial, avoidable memory cost; consider generating ranges lazily per batch instead. Also, itertools.batched(..., settings.event_logs_max_concurrency) will raise if the concurrency is <= 0, so validating/clamping that setting would prevent runtime crashes on misconfiguration.

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +142
app_state = AppState()
encoded_message = encode_typed_data(full_message=full_message)
signed_msg = app_state.validators_manager_account.sign_message(encoded_message)

return HexStr(signed_msg.signature.hex())
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_create_and_sign_message returns signed_msg.signature.hex() without a 0x prefix, so the API will emit a non-standard hex string (and may fail downstream HexStr parsing/on-chain tooling). Prefix the signature with 0x (e.g., via Web3.to_hex(signed_msg.signature) or manual prefixing).

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +93
# Compute validators manager signature
validators_manager_signature: HexStr | None = None

if is_signatures_ready_for_all_validators:
validators_registry_root = await validators_registry_contract.get_registry_root()
validators_manager_signature = get_validators_manager_signature_register(
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are zero validators in the /register loop (e.g., no unregistered keys or empty amounts), is_signatures_ready_for_all_validators stays True and the code will generate a validators-manager signature over an empty validators list. Consider explicitly rejecting empty requests/results (or forcing the flag to False) to avoid producing a potentially meaningful signature for an empty payload.

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +123
for public_key, amount in zip(request.public_keys, request.amounts):
validator = Validator(
public_key=public_key,
vault=request.vault,
amount=amount,
validator_type=ValidatorType.V2,
validator_index=0,
created_at=0,
deposit_signature=BLSSignature(empty_signature),
)
validators.append(validator)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/fund uses zip(request.public_keys, request.amounts), which silently truncates if the list lengths differ. Add an explicit length check and return a 4xx error on mismatch to avoid signing incorrect/partial funding payloads.

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +112
def _encode_withdrawals(public_keys: list[HexStr], amounts: list[Gwei]) -> bytes:
data = b''
for public_key, amount in zip(public_keys, amounts):
data += Web3.to_bytes(hexstr=public_key)
data += amount.to_bytes(8, byteorder='big')

return data


def _encode_consolidations(
source_public_keys: list[HexStr], target_public_keys: list[HexStr]
) -> bytes:
validators_data = b''
for source_key, target_key in zip(source_public_keys, target_public_keys):
validators_data += Web3.to_bytes(hexstr=source_key)
validators_data += Web3.to_bytes(hexstr=target_key)
return validators_data
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both _encode_withdrawals and _encode_consolidations use zip(...), so mismatched list lengths will silently drop trailing items and still produce a signature. It’s safer to validate equal lengths (and non-empty where required) and raise a clear error rather than signing a truncated payload.

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +102
validator.deposit_signature_shares[request.share_index] = BLSSignature(
Web3.to_bytes(hexstr=share.deposit_signature)
)

return ExitSignatureShareResponse()
if len(validator.deposit_signature_shares) >= settings.signature_threshold:
# Reconstruct and validate deposit signature
deposit_signature = reconstruct_shared_bls_signature(
validator.deposit_signature_shares
)
if not validate_deposit_signature(
validator.public_key,
Web3.to_bytes(hexstr=validator.withdrawal_credentials),
validator.amount,
deposit_signature,
):
raise HTTPException(
status_code=400,
detail=(
f'invalid deposit signature for public_key={share.public_key},'
f' share_index={request.share_index}'
),
)

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue for deposit shares: if deposit-signature reconstruction/validation fails, the share is already persisted in validator.deposit_signature_shares, and resubmitting for that share_index will be skipped. Roll back the stored share (or avoid storing until successful) before raising the HTTP 400 to prevent a single bad submission from bricking the validator.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants