Skip to content
Open
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
1,186 changes: 617 additions & 569 deletions poetry.lock

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ package-mode = false
python = ">=3.12,<3.13"
python-decouple = "==3.8"
sentry-sdk = "==1.45.1"
py-ecc = "==6.0.0"
py-ecc = "==8.0.0"
gql = {extras = ["aiohttp"], version = "==3.5.0"}
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The sw-utils version is being downgraded from v0.11.0 to v0.10.6. This is a backwards change that could break functionality or lose features. If this is intentional due to compatibility issues with Web3.py v7, it should be documented in the PR description or comments.

Suggested change
gql = {extras = ["aiohttp"], version = "==3.5.0"}
gql = {extras = ["aiohttp"], version = "==3.5.0"}
# sw-utils is intentionally downgraded to v0.10.6 for compatibility with Web3.py v7. See PR description for details.

Copilot uses AI. Check for mistakes.
multiproof = { git = "https://github.com/stakewise/multiproof.git", rev = "v0.1.8" }
sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.11.0"}
sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.10.6"}
staking-deposit = { git = "https://github.com/ethereum/staking-deposit-cli.git", rev = "v2.8.0" }
pycryptodomex = "3.19.1"
click = "==8.2.1"
Expand All @@ -22,7 +21,7 @@ prometheus-client = "==0.17.1"
psycopg2 = "==2.9.9"
pyyaml = "==6.0.1"
python-json-logger = "==2.0.7"
aiohttp = "==3.12.14"
aiohttp = "==3.13.0"
psutil = "==7.0.0"

[tool.poetry.group.dev.dependencies]
Expand Down Expand Up @@ -141,5 +140,4 @@ ignore_names = [
"validators_root", # ApprovalRequest
"previous_version", "current_version", "genesis_validators_root", "fork_info", "voluntary_exit", "deposit", # remote.py
"rounding", # decimal context
"async_cache_and_return_session" # web3py patch
]
36 changes: 30 additions & 6 deletions src/commands/consolidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from web3 import Web3
from web3.types import Gwei, Wei

from src.common.clients import setup_clients
from src.common.consensus import get_chain_latest_head
from src.common.clients import close_clients, setup_clients
from src.common.consensus import get_chain_justified_head
from src.common.contracts import VaultContract
from src.common.execution import (
check_gas_price,
Expand Down Expand Up @@ -262,7 +262,7 @@ def consolidate(
sys.exit(1)


# pylint: disable-next=too-many-locals,too-many-arguments
# pylint: disable-next=too-many-arguments
async def main(
vault_address: ChecksumAddress,
source_public_keys: list[HexStr] | None,
Expand All @@ -271,6 +271,32 @@ async def main(
no_switch_consolidation: bool,
max_consolidation_request_fee_gwei: Gwei,
no_confirm: bool,
) -> None:
setup_logging()
await setup_clients()
try:
await process(
vault_address=vault_address,
source_public_keys=source_public_keys,
target_public_key=target_public_key,
exclude_public_keys=exclude_public_keys,
no_switch_consolidation=no_switch_consolidation,
max_consolidation_request_fee_gwei=Gwei(max_consolidation_request_fee_gwei),
no_confirm=no_confirm,
)
finally:
await close_clients()


# pylint: disable-next=too-many-locals,too-many-arguments
async def process(
vault_address: ChecksumAddress,
source_public_keys: list[HexStr] | None,
target_public_key: HexStr | None,
exclude_public_keys: set[HexStr],
no_switch_consolidation: bool,
max_consolidation_request_fee_gwei: Gwei,
no_confirm: bool,
) -> None:
# pylint: disable=line-too-long
"""
Expand All @@ -279,9 +305,7 @@ async def main(
Check validation details: https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#new-process_consolidation_request
Then send the request to the contract.
"""
setup_logging()
await setup_clients()
chain_head = await get_chain_latest_head()
chain_head = await get_chain_justified_head()

await _check_validators_manager(vault_address)
await _check_consolidations_queue(chain_head)
Expand Down
16 changes: 15 additions & 1 deletion src/commands/exit_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from web3 import Web3
from web3.types import Gwei, Wei

from src.common.clients import setup_clients
from src.common.clients import close_clients, setup_clients
from src.common.consensus import get_chain_justified_head
from src.common.contracts import VaultContract
from src.common.execution import get_withdrawal_request_fee
Expand Down Expand Up @@ -198,6 +198,20 @@ async def main(
) -> None:
setup_logging()
await setup_clients()
try:
await process(
vault_address=vault_address,
indexes=indexes,
count=count,
no_confirm=no_confirm,
)
finally:
await close_clients()


async def process(
vault_address: ChecksumAddress, count: int | None, indexes: list[int], no_confirm: bool
) -> None:

await check_vault_version()
await check_validators_manager()
Expand Down
24 changes: 23 additions & 1 deletion src/commands/recover.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
from eth_utils import add_0x_prefix
from sw_utils.consensus import EXITED_STATUSES, ValidatorStatus

from src.common.clients import consensus_client, execution_client, setup_clients
from src.common.clients import (
close_clients,
consensus_client,
execution_client,
setup_clients,
)
from src.common.contracts import VaultContract
from src.common.credentials import CredentialManager
from src.common.logging import LOG_LEVELS, setup_logging
Expand Down Expand Up @@ -148,6 +153,23 @@ async def main(
) -> None:
setup_logging()
await setup_clients()
try:
await process(
mnemonic=mnemonic,
per_keystore_password=per_keystore_password,
no_confirm=no_confirm,
operator_config=operator_config,
)
finally:
await close_clients()


async def process(
mnemonic: str,
per_keystore_password: bool,
no_confirm: bool,
operator_config: OperatorConfig,
) -> None:

validators: dict[HexStr, ValidatorStatus | None] = await _fetch_registered_validators(
settings.vault
Expand Down
10 changes: 8 additions & 2 deletions src/commands/setup_remote_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from eth_typing import ChecksumAddress
from sw_utils import chunkify

from src.common.clients import setup_clients
from src.common.clients import close_clients, setup_clients
from src.common.contracts import VaultContract
from src.common.logging import LOG_LEVELS, setup_logging
from src.common.startup_check import wait_for_execution_node
Expand Down Expand Up @@ -138,11 +138,17 @@ def setup_remote_signer(
sys.exit(1)


# pylint: disable-next=too-many-locals
async def main(vault: ChecksumAddress | None) -> None:
setup_logging()
await setup_clients()
try:
await process(vault)
finally:
await close_clients()


# pylint: disable-next=too-many-locals
async def process(vault: ChecksumAddress | None) -> None:
keystore_files = LocalKeystore.list_keystore_files()
if len(keystore_files) == 0:
raise click.ClickException('Keystores not found.')
Expand Down
10 changes: 7 additions & 3 deletions src/commands/start/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import src
from src.commands.nodes.node_start import main as run_nodes
from src.common.clients import setup_clients
from src.common.clients import close_clients, setup_clients
from src.common.consensus import get_chain_finalized_head
from src.common.execution import WalletTask
from src.common.logging import setup_logging
Expand Down Expand Up @@ -38,10 +38,15 @@ async def start_base() -> None:
"""Bootstrap operator service and start periodic tasks."""
setup_logging()
setup_sentry()
log_start()
await setup_clients()
try:
await process()
finally:
await close_clients()

log_start()

async def process() -> None:
background_tasks = set()

if settings.run_nodes:
Expand All @@ -55,7 +60,6 @@ async def start_base() -> None:

# Keep track of background tasks to prevent them from being garbage collected
background_tasks.add(run_nodes_task)

if not settings.skip_startup_checks:
await startup_checks()

Expand Down
14 changes: 11 additions & 3 deletions src/common/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)
from sw_utils.graph.client import GraphClient as SWGraphClient
from web3 import AsyncWeb3
from web3.middleware.signing import async_construct_sign_and_send_raw_middleware
from web3.middleware import SignAndSendRawMiddlewareBuilder

import src
from src.common.wallet import wallet
Expand Down Expand Up @@ -59,8 +59,10 @@ async def setup(self) -> None:
# Account is required when emitting transactions.
# For read-only queries account may be omitted.
if wallet.can_load():
w3.middleware_onion.add(
await async_construct_sign_and_send_raw_middleware(wallet.account)
w3.middleware_onion.inject(
# pylint: disable-next=no-value-for-parameter
SignAndSendRawMiddlewareBuilder.build(wallet.account),
layer=0,
)
w3.eth.default_account = wallet.address

Expand Down Expand Up @@ -129,3 +131,9 @@ async def fetch_json(self, ipfs_hash: str) -> dict | list:
async def setup_clients() -> None:
await execution_client.setup() # type: ignore
await execution_non_retry_client.setup() # type: ignore


async def close_clients() -> None:
await execution_client.provider.disconnect()
await execution_non_retry_client.provider.disconnect()
await consensus_client.disconnect()
10 changes: 5 additions & 5 deletions src/common/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def events(self) -> AsyncContractEvents:
return self.contract.events

def encode_abi(self, fn_name: str, args: list | None = None) -> HexStr:
return self.contract.encodeABI(fn_name=fn_name, args=args)
return self.contract.encode_abi(fn_name, args=args)

async def _get_last_event(
self,
Expand All @@ -76,8 +76,8 @@ async def _get_last_event(
blocks_range = settings.events_blocks_range_interval
while to_block >= from_block:
events = await event.get_logs(
fromBlock=BlockNumber(max(to_block - blocks_range, from_block)),
toBlock=to_block,
from_block=BlockNumber(max(to_block - blocks_range, from_block)),
to_block=to_block,
argument_filters=argument_filters,
)
if events:
Expand All @@ -95,8 +95,8 @@ async def _get_events(
blocks_range = settings.events_blocks_range_interval
while to_block >= from_block:
range_events = await event.get_logs(
fromBlock=from_block,
toBlock=BlockNumber(min(from_block + blocks_range, to_block)),
from_block=from_block,
to_block=BlockNumber(min(from_block + blocks_range, to_block)),
)
if range_events:
events.extend(range_events)
Expand Down
3 changes: 0 additions & 3 deletions src/common/credentials.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import time
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from functools import cached_property
from multiprocessing import Pool
Expand Down Expand Up @@ -29,7 +28,6 @@
)
from sw_utils.typings import Bytes32
from web3 import Web3
from web3._utils import request

from src.common.typings import ValidatorType
from src.config.networks import NETWORKS
Expand Down Expand Up @@ -174,7 +172,6 @@ def _generate_credentials_chunk(
) -> list[Credential]:
# Hack to run web3 sessions in multiprocessing mode
# pylint: disable-next=protected-access
request._async_session_pool = ThreadPoolExecutor(max_workers=1)

credentials: list[Credential] = []
for index in indexes:
Expand Down
Loading
Loading