Source code for golosscripts.monitor
import logging
import time
from datetime import datetime
from typing import List, Union
from golos.utils import parse_time
from golos.witness import Witness
from .golos_helper import GolosHelper
[docs]log = logging.getLogger(__name__)
[docs]class Monitor(GolosHelper):
"""Performs monitoring of missed blocks and automatically switches signing key."""
def __init__(
self,
node: Union[str, List[str]],
keys: Union[str, List[str]],
witness: str,
witness_pubkey: str,
miss_window: int = 3600,
allowed_misses: int = 1,
) -> None:
"""
:param str,list node: golos node to connect to
:param str,list keys: witness active keys
:param str witness: witness name to update feed for
:param str witness_pubkey: witness signing key
:param int miss_window: time in seconds to observe block signing misses
:param int allowed_misses: number of missed blocks per miss_window to allow before switching a key
"""
self.witness = witness
self.witness_pubkey = witness_pubkey
self.miss_window = miss_window
self.allowed_misses = allowed_misses
self.miss_count = 0
self.miss_window_start_time = datetime.now()
# Helper setup
super().__init__(nodes=node, keys=keys, num_retries=-1, mode='head')
[docs] def check_node_sync(self) -> bool:
"""Checks if API node is in sync."""
props = self.get_dynamic_global_properties()
chain_time = parse_time(props['time'])
time_diff = datetime.utcnow() - chain_time
if time_diff.total_seconds() > 6:
log.warning('node out of sync, timediff: {}, waiting...'.format(time_diff.total_seconds()))
return False
return True
[docs] def set_miss_data(self, witness: Witness) -> None:
self.miss_count = witness['total_missed']
self.miss_window_start_time = datetime.now()
[docs] def check_miss_window_end(self) -> bool:
now = datetime.now()
time_delta = now - self.miss_window_start_time
if time_delta.total_seconds() > self.miss_window:
return True
return False
[docs] def do_witness_check(self, witness: Witness) -> None:
"""Check whether witness configured on another node is missing blocks."""
current_miss_count = witness['total_missed']
miss_diff = current_miss_count - self.miss_count
log.debug('current miss diff: {}'.format(miss_diff))
if miss_diff > self.allowed_misses:
log.info(
'switching witness key, missed blocks exceed threshold: {} > {}'.format(miss_diff, self.allowed_misses)
)
self.witness_update(self.witness_pubkey, witness['url'], witness['props'], account=self.witness)
elif self.check_miss_window_end():
log.debug('miss_window ended, beginning a new one')
self.set_miss_data(witness)
[docs] def run_once(self) -> None:
"""Do miss check once."""
in_sync = self.check_node_sync()
if not in_sync:
return
witness = Witness(self.witness)
producing_locally = witness['signing_key'] == self.witness_pubkey
if producing_locally:
if self.miss_count:
log.info('stopping monitoring, reason: local block production')
self.miss_count = 0
return
elif not self.miss_count:
self.set_miss_data(witness)
log.info('starting/resuming monitoring, current miss count: {}'.format(self.miss_count))
return
self.do_witness_check(witness)
[docs] def run_forever(self) -> None:
"""Main loop."""
while True:
try:
self.run_once()
except Exception:
log.exception('Exception in main loop')
time.sleep(30)