Skip to content

Commit 5a9d849

Browse files
Fix parsing block timestamp (#825)
1 parent 08c150f commit 5a9d849

File tree

7 files changed

+74
-22
lines changed

7 files changed

+74
-22
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ ignore = [
147147
target-version = "py311"
148148
extend-select = ["B", "C4", "FA", "G", "I", "PTH", "Q", "RET", "RUF", "TCH", "UP"]
149149
flake8-quotes = { inline-quotes = "single", multiline-quotes = "double" }
150-
isort = { force-single-line = true }
150+
isort = { force-single-line = true, known-first-party = ["dipdup"] }
151151

152152
[tool.mypy]
153153
python_version = "3.11"

src/dipdup/datasources/evm_node.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from collections import defaultdict
44
from collections.abc import Awaitable
55
from collections.abc import Callable
6+
from dataclasses import dataclass
7+
from dataclasses import field
68
from typing import Any
79
from uuid import uuid4
810

@@ -43,6 +45,13 @@
4345
RollbackCallback = Callable[['IndexDatasource', MessageType, int, int], Awaitable[None]]
4446

4547

48+
@dataclass
49+
class NodeHead:
50+
event: asyncio.Event = field(default_factory=asyncio.Event)
51+
hash: str | None = None
52+
timestamp: int | None = None
53+
54+
4655
class EvmNodeDatasource(IndexDatasource[EvmNodeDatasourceConfig]):
4756
# TODO: Make dynamic
4857
_default_http_config = HttpConfig(ratelimit_sleep=30)
@@ -54,8 +63,7 @@ def __init__(self, config: EvmNodeDatasourceConfig, merge_subscriptions: bool =
5463
self._realtime: asyncio.Event = asyncio.Event()
5564
self._requests: dict[str, tuple[asyncio.Event, Any]] = {}
5665
self._subscription_ids: dict[str, EvmNodeSubscription] = {}
57-
self._head_hashes: dict[int, str] = {}
58-
self._head_events: defaultdict[int, asyncio.Event] = defaultdict(asyncio.Event)
66+
self._heads: defaultdict[int, NodeHead] = defaultdict(NodeHead)
5967

6068
self._on_connected_callbacks: set[EmptyCallback] = set()
6169
self._on_disconnected_callbacks: set[EmptyCallback] = set()
@@ -267,28 +275,30 @@ async def _on_message(self, message: Message) -> None:
267275
raise DatasourceError(f'Unknown message: {data}', self.name)
268276

269277
async def _handle_subscription(self, subscription: EvmNodeSubscription, data: Any) -> None:
270-
msg_type = MessageType()
271-
272278
if isinstance(subscription, EvmNodeNewHeadsSubscription):
273279
head = EvmNodeHeadData.from_json(data)
274280
level = int(head.number, 16)
275281

276-
known_hash = self._head_hashes.get(level, None)
277-
current_level = max(self._head_hashes.keys() or (level,))
278-
self._head_hashes[level] = head.hash
279-
if known_hash != head.hash:
280-
await self.emit_rollback(msg_type, from_level=current_level, to_level=level - 1)
282+
known_hash = self._heads[level].hash
283+
if known_hash not in (head.hash, None):
284+
await self.emit_rollback(
285+
MessageType(),
286+
from_level=max(self._heads.keys() or (level,)),
287+
to_level=level - 1,
288+
)
281289

290+
self._heads[level].hash = head.hash
291+
self._heads[level].timestamp = int(head.timestamp, 16)
282292
await self.emit_head(head)
283-
self._head_events[level].set()
293+
self._heads[level].event.set()
284294

285295
elif isinstance(subscription, EvmNodeLogsSubscription):
286-
# FIXME: Take from the head instead.
287-
timestamp = int(time.time())
296+
level = int(data['blockNumber'], 16)
297+
await self._heads[level].event.wait()
298+
timestamp = self._heads[level].timestamp
299+
if timestamp is None:
300+
raise FrameworkException('Head cached but timestamp is None')
288301
logs = EvmNodeLogData.from_json(data, timestamp)
289-
level = int(logs.block_number, 16)
290-
291-
await self._head_events[level].wait()
292302
await self.emit_logs(logs)
293303

294304
elif isinstance(subscription, EvmNodeSyncingData):

src/dipdup/indexes/evm_subsquid_events/index.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ async def _synchronize(self, sync_level: int) -> None:
167167
block = await self.random_node.get_block_by_level(level)
168168
if block is None:
169169
raise FrameworkException(f'Block {level} not found')
170-
parsed_level_logs = tuple(EvmNodeLogData.from_json(log, int(block['timestamp'])) for log in level_logs)
170+
timestamp = int(block['timestamp'], 16)
171+
parsed_level_logs = tuple(EvmNodeLogData.from_json(log, timestamp) for log in level_logs)
171172
await self._process_level_events(parsed_level_logs, self.topics, sync_level)
172173

173174
else:

tests/configs/demo_evm_events.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
spec_version: 2.0
2+
package: demo_evm_events
3+
4+
datasources:
5+
ethscan:
6+
kind: abi.etherscan
7+
8+
# mainnet_node:
9+
# kind: evm.node
10+
# url: https://mainnet.infura.io/v3/${INFURA_KEY:-''}
11+
# ws_url: wss://mainnet.infura.io/ws/v3/${INFURA_KEY:-''}
12+
13+
mainnet_subsquid:
14+
kind: evm.subsquid
15+
url: ${ARCHIVE_URL:-https://v2.archive.subsquid.io/network/ethereum-mainnet}
16+
# node: mainnet_node
17+
18+
contracts:
19+
eth_usdt:
20+
kind: evm
21+
address: 0xdac17f958d2ee523a2206206994597c13d831ec7
22+
typename: eth_usdt
23+
24+
indexes:
25+
eth_usdt_events:
26+
kind: evm.subsquid.events
27+
datasource: mainnet_subsquid
28+
handlers:
29+
- callback: on_transfer
30+
contract: eth_usdt
31+
name: Transfer
32+
first_level: 18077421
33+
last_level: 18077421

tests/test_demos.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,8 @@ async def assert_init(package: str) -> None:
135135

136136

137137
async def assert_run_dex() -> None:
138-
from tortoise.transactions import in_transaction
139-
140138
import demo_dex.models
139+
from tortoise.transactions import in_transaction
141140

142141
trades = await demo_dex.models.Trade.filter().count()
143142
positions = await demo_dex.models.Position.filter().count()
@@ -180,6 +179,13 @@ async def assert_run_raw() -> None:
180179
assert migrations == 2
181180

182181

182+
async def assert_run_evm_events() -> None:
183+
import demo_evm_events.models
184+
185+
holders = await demo_evm_events.models.Holder.filter().count()
186+
assert holders == 5296
187+
188+
183189
async def assert_run_dao() -> None:
184190
import demo_dao.models
185191

@@ -226,6 +232,8 @@ async def assert_run_dao() -> None:
226232
('demo_factories.yml', 'demo_factories', 'init', partial(assert_init, 'demo_factories')),
227233
('demo_raw.yml', 'demo_raw', 'run', assert_run_raw),
228234
('demo_raw.yml', 'demo_raw', 'init', partial(assert_init, 'demo_raw')),
235+
('demo_evm_events.yml', 'demo_evm_events', 'run', assert_run_evm_events),
236+
('demo_evm_events.yml', 'demo_evm_events', 'init', partial(assert_init, 'demo_evm_events')),
229237
)
230238

231239

tests/test_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from typing import Any
66

77
import orjson as json
8-
98
from demo_domains.types.name_registry.tezos_storage import NameRegistryStorage
9+
1010
from dipdup.indexes.tezos_tzkt_operations.parser import deserialize_storage
1111
from dipdup.models.tezos_tzkt import TzktOperationData
1212
from tests.types.asdf.storage import AsdfStorage

tests/test_rollback.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from contextlib import AsyncExitStack
22
from datetime import datetime
33

4-
from tortoise.expressions import F
5-
64
import demo_domains.models as domains_models
75
import demo_nft_marketplace.models as hen_models
6+
from tortoise.expressions import F
7+
88
from dipdup.config import DipDupConfig
99
from dipdup.context import HookContext
1010
from dipdup.dipdup import DipDup

0 commit comments

Comments
 (0)