Skip to content

Commit 1589e34

Browse files
Skip anonymous events; node realtime watchdog (#911)
1 parent 5d930c5 commit 1589e34

File tree

5 files changed

+51
-1
lines changed

5 files changed

+51
-1
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning].
66

7+
## [Unreleased]
8+
9+
### Added
10+
11+
- cli: Added `DIPDUP_CONFIG` and `DIPDUP_ENV_FILE` environment variables corresponding to `--config` and `--env-file` options.
12+
13+
### Fixed
14+
15+
- evm.node: Fixed crash on anonymous event logs during the last mile indexing.
16+
- evm.node: Raise an exception when no realtime messages have been received in `http.connection_timeout` seconds.
17+
718
## [7.2.0] - 2023-11-30
819

920
### Added

src/dipdup/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ def _skip_cli_group() -> bool:
191191
help='A path to DipDup project config.',
192192
default=[ROOT_CONFIG],
193193
metavar='PATH',
194+
envvar='DIPDUP_CONFIG',
194195
)
195196
@click.option(
196197
'--env-file',
@@ -200,6 +201,7 @@ def _skip_cli_group() -> bool:
200201
help='A path to .env file containing `KEY=value` strings.',
201202
default=[],
202203
metavar='PATH',
204+
envvar='DIPDUP_ENV_FILE',
203205
)
204206
@click.pass_context
205207
@_cli_wrapper

src/dipdup/datasources/evm_node.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from dipdup.pysignalr import WebsocketMessage
3636
from dipdup.pysignalr import WebsocketProtocol
3737
from dipdup.pysignalr import WebsocketTransport
38+
from dipdup.utils import Watchdog
3839

3940
WEB3_CACHE_SIZE = 256
4041

@@ -66,6 +67,7 @@ def __init__(self, config: EvmNodeDatasourceConfig, merge_subscriptions: bool =
6667
self._subscription_ids: dict[str, EvmNodeSubscription] = {}
6768
self._logs_queue: Queue[dict[str, Any]] = Queue()
6869
self._heads: defaultdict[int, NodeHead] = defaultdict(NodeHead)
70+
self._watchdog: Watchdog = Watchdog(self._http_config.connection_timeout)
6971

7072
self._on_connected_callbacks: set[EmptyCallback] = set()
7173
self._on_disconnected_callbacks: set[EmptyCallback] = set()
@@ -109,14 +111,22 @@ async def run(self) -> None:
109111
await asyncio.gather(
110112
self._ws_loop(),
111113
self._log_processor_loop(),
114+
self._watchdog.run(),
112115
)
113116

114117
async def _log_processor_loop(self) -> None:
115118
while True:
116119
log_json = await self._logs_queue.get()
117120
level = int(log_json['blockNumber'], 16)
118121

119-
await self._heads[level].event.wait()
122+
try:
123+
await asyncio.wait_for(
124+
self._heads[level].event.wait(),
125+
timeout=self._http_config.connection_timeout,
126+
)
127+
except asyncio.TimeoutError as e:
128+
msg = f'Head for level {level} not received in {self._http_config.connection_timeout} seconds'
129+
raise FrameworkException(msg) from e
120130
timestamp = self._heads[level].timestamp
121131
if timestamp is None:
122132
raise FrameworkException('Head received but timestamp is None')
@@ -275,6 +285,7 @@ async def _on_message(self, message: Message) -> None:
275285
raise FrameworkException(f'Unknown message type: {type(message)}')
276286

277287
data = message.data
288+
self._watchdog.reset()
278289

279290
if 'id' in data:
280291
request_id = data['id']

src/dipdup/indexes/evm_subsquid_events/matcher.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ def match_events(
9696
matched_handlers: deque[MatchedEventsT] = deque()
9797

9898
for event in events:
99+
if not event.topics:
100+
continue
101+
99102
for handler_config in handlers:
100103
typename = handler_config.contract.module_name
101104
name = handler_config.name

src/dipdup/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import importlib
23
import logging
34
import pkgutil
@@ -228,3 +229,25 @@ def json_dumps(obj: Any | str, option: int | None = orjson.OPT_INDENT_2) -> byte
228229
default=_default_for_decimals,
229230
option=option,
230231
)
232+
233+
234+
class Watchdog:
235+
def __init__(self, timeout: int) -> None:
236+
self._watchdog = asyncio.Event()
237+
self._timeout = timeout
238+
239+
def reset(self) -> None:
240+
self._watchdog.set()
241+
self._watchdog.clear()
242+
243+
async def run(self) -> None:
244+
while True:
245+
await asyncio.sleep(self._timeout)
246+
try:
247+
await asyncio.wait_for(
248+
self._watchdog.wait(),
249+
timeout=self._timeout,
250+
)
251+
except asyncio.TimeoutError as e:
252+
msg = f'Watchdog timeout; no messages received in {self._timeout} seconds'
253+
raise FrameworkException(msg) from e

0 commit comments

Comments
 (0)