generated from lidofinance/python-base-template
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/val 1404 eip 7251 head watcher alerts for new el requests (#58)
* feat(val-1404): alerts for new EL requests * fix: pin alertmanager to compatible version because headwatcher use the deprecated /api/v1/alerts endpoint * chore: .gitignore Docker volumes * docs: update documentation to align with code * fix: correct keyword casing to prevent Docker warnings; fix LegacyKeyValueFormat for ENV * feat(val-1404): group similar alerts * feat(val-1404): lazy VALID_WITHDRAWAL_ADDRESSES --------- Co-authored-by: AlexanderLukin <alexanderlukin9@gmail.com>
- Loading branch information
1 parent
dc36bc7
commit 6b973ff
Showing
20 changed files
with
674 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -131,3 +131,6 @@ dmypy.json | |
|
||
# IDE | ||
.idea/ | ||
|
||
# Docker | ||
.volumes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import logging | ||
|
||
from unsync import unsync | ||
|
||
from src.alerts.common import CommonAlert | ||
from src.handlers.handler import WatcherHandler | ||
from src.handlers.helpers import beaconchain, validator_pubkey_link | ||
from src.metrics.prometheus.duration_meter import duration_meter | ||
from src.providers.consensus.typings import ConsolidationRequest, FullBlockInfo | ||
|
||
logger = logging.getLogger() | ||
|
||
|
||
class ConsolidationHandler(WatcherHandler): | ||
@unsync | ||
@duration_meter() | ||
def handle(self, watcher, head: FullBlockInfo): | ||
if not head.message.body.execution_requests or not head.message.body.execution_requests.consolidations: | ||
logger.info({"msg": f"No consolidation requests in block [{head.message.slot}]"}) | ||
return | ||
|
||
slot = head.message.slot | ||
withdrawal_address, source_pubkey, target_pubkey = [], [], [] | ||
for consolidation in head.message.body.execution_requests.consolidations: | ||
if consolidation.source_address in watcher.valid_withdrawal_addresses: | ||
withdrawal_address.append(consolidation) | ||
elif consolidation.source_pubkey in watcher.user_keys: | ||
source_pubkey.append(consolidation) | ||
elif consolidation.target_pubkey in watcher.user_keys: | ||
target_pubkey.append(consolidation) | ||
# in the future we should check the type of validator WC: | ||
# if it is 0x02 and source_address == WCs of source validator - It's donation! | ||
|
||
if withdrawal_address: | ||
self._send_withdrawals_address(watcher, slot, withdrawal_address) | ||
if source_pubkey: | ||
self._send_source_pubkey(watcher, slot, source_pubkey) | ||
if target_pubkey: | ||
self._send_target_pubkey(watcher, slot, target_pubkey) | ||
|
||
def _send_withdrawals_address(self, watcher, slot, consolidations: list[ConsolidationRequest]): | ||
alert = CommonAlert(name="HeadWatcherConsolidationSourceWithdrawalAddress", severity="critical") | ||
summary = "🚨🚨🚨 Validator consolidation was requested from Withdrawal Vault source address" | ||
self._send_alert(watcher, slot, alert, summary, consolidations) | ||
|
||
def _send_source_pubkey(self, watcher, slot, consolidations: list[ConsolidationRequest]): | ||
alert = CommonAlert(name="HeadWatcherConsolidationUserSourcePubkey", severity="info") | ||
summary = "⚠️⚠️⚠️ Consolidation was requested for our validators" | ||
self._send_alert(watcher, slot, alert, summary, consolidations) | ||
|
||
def _send_target_pubkey(self, watcher, slot, consolidations: list[ConsolidationRequest]): | ||
alert = CommonAlert(name="HeadWatcherConsolidationUserTargetPubkey", severity="info") | ||
summary = "⚠️⚠️⚠️ Someone attempts to consolidate their validators to our validators" | ||
self._send_alert(watcher, slot, alert, summary, consolidations) | ||
|
||
def _send_alert(self, watcher, slot: str, alert: CommonAlert, summary: str, | ||
consolidations: list[ConsolidationRequest]): | ||
description = '\n\n'.join(self._describe_consolidation(c, watcher.user_keys) for c in consolidations) | ||
description += f'\n\nSlot: {beaconchain(slot)}' | ||
self.send_alert(watcher, alert.build_body(summary, description)) | ||
|
||
@staticmethod | ||
def _describe_consolidation(consolidation: ConsolidationRequest, keys): | ||
return '\n'.join([ | ||
f'Request source address: {consolidation.source_address}', | ||
f'Source: {validator_pubkey_link(consolidation.source_pubkey, keys)}', | ||
f'Target: {validator_pubkey_link(consolidation.target_pubkey, keys)}', | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import logging | ||
|
||
from unsync import unsync | ||
|
||
from src.alerts.common import CommonAlert | ||
from src.handlers.handler import WatcherHandler | ||
from src.handlers.helpers import beaconchain, validator_pubkey_link | ||
from src.keys_source.base_source import NamedKey | ||
from src.metrics.prometheus.duration_meter import duration_meter | ||
from src.providers.consensus.typings import FullBlockInfo, WithdrawalRequest | ||
|
||
logger = logging.getLogger() | ||
|
||
|
||
class ElTriggeredExitHandler(WatcherHandler): | ||
@unsync | ||
@duration_meter() | ||
def handle(self, watcher, head: FullBlockInfo): | ||
if not head.message.body.execution_requests or not head.message.body.execution_requests.withdrawals: | ||
logger.debug({"msg": f"No withdrawals requests in block [{head.message.slot}]"}) | ||
return | ||
|
||
slot = head.message.slot | ||
our_withdrawal_address, our_validators = [], [] | ||
for withdrawal in head.message.body.execution_requests.withdrawals: | ||
if withdrawal.source_address in watcher.valid_withdrawal_addresses: | ||
our_withdrawal_address.append(withdrawal) | ||
elif withdrawal.validator_pubkey in watcher.user_keys: | ||
our_validators.append(withdrawal) | ||
|
||
if our_withdrawal_address: | ||
self._send_withdrawal_address_alerts(watcher, slot, our_withdrawal_address) | ||
if our_validators: | ||
self._send_our_validators_alert(watcher, slot, our_validators) | ||
|
||
def _send_withdrawal_address_alerts(self, watcher, slot: str, withdrawals: list[WithdrawalRequest]): | ||
alert = CommonAlert(name="HeadWatcherELWithdrawalFromUserWithdrawalAddress", severity="critical") | ||
summary = "🚨🚨🚨 Our validator triggered withdrawal was requested from our Withdrawal Vault address" | ||
description = '\n\n'.join(map(lambda w: self._describe_withdrawal(w, watcher.user_keys), withdrawals)) | ||
self._send_alert(watcher, alert, summary, description, slot) | ||
|
||
def _send_our_validators_alert(self, watcher, slot: str, withdrawals: list[WithdrawalRequest]): | ||
alert = CommonAlert(name="HeadWatcherUserELWithdrawal", severity="info") | ||
summary = "⚠️⚠️⚠️ Our validator triggered withdrawal was requested" | ||
description = '\n\n'.join(map(lambda w: self._describe_withdrawal(w, watcher.user_keys), withdrawals)) | ||
self._send_alert(watcher, alert, summary, description, slot) | ||
|
||
def _send_alert(self, watcher, alert: CommonAlert, summary: str, description: str, slot: str): | ||
description += f'\n\nSlot: {beaconchain(slot)}' | ||
self.send_alert(watcher, alert.build_body(summary, description)) | ||
|
||
@staticmethod | ||
def _describe_withdrawal(withdrawal: WithdrawalRequest, user_keys: dict[str, NamedKey]) -> str: | ||
return '\n'.join([ | ||
f'Source address: {withdrawal.source_address}', | ||
f'Validator: {validator_pubkey_link(withdrawal.validator_pubkey, user_keys)}', | ||
f'Amount: {withdrawal.amount}', | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from src.keys_source.base_source import NamedKey | ||
from src.variables import NETWORK_NAME | ||
|
||
BEACONCHAIN_URL_TEMPLATE = "[{0}](https://{1}.beaconcha.in/slot/{0})" | ||
BEACONCHAIN_VALIDATOR_URL_TEMPLATE = "[{0}](https://{1}.beaconcha.in/validator/{2})" | ||
|
||
|
||
def beaconchain(slot) -> str: | ||
return BEACONCHAIN_URL_TEMPLATE.format(slot, NETWORK_NAME) | ||
|
||
|
||
def validator_link(title: str, pubkey: str) -> str: | ||
return BEACONCHAIN_VALIDATOR_URL_TEMPLATE.format(title, NETWORK_NAME, pubkey) | ||
|
||
def validator_pubkey_link(pubkey: str, keys: dict[str, NamedKey]) -> str: | ||
operator = keys[pubkey].operatorName if pubkey in keys else '' | ||
spacer = ' ' if operator else '' | ||
title = f'{operator}{spacer}{pubkey}' | ||
return validator_link(title, pubkey) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.