Skip to content

Commit 64fb454

Browse files
Index state management and Hasura bugfixes (#117)
1 parent 9bd38cd commit 64fb454

File tree

6 files changed

+108
-113
lines changed

6 files changed

+108
-113
lines changed

src/dipdup/datasources/datasource.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def __call__(self, datasource: 'IndexDatasource', operations: List[OperationData
2323

2424

2525
class BigMapsCallback(Protocol):
26-
def __call__(self, datasource: 'IndexDatasource', big_maps: List[BigMapData]) -> Awaitable[None]:
26+
def __call__(self, datasource: 'IndexDatasource', big_maps: List[BigMapData], block: HeadBlockData) -> Awaitable[None]:
2727
...
2828

2929

src/dipdup/datasources/tzkt/datasource.py

Lines changed: 34 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ def sync_level(self) -> Optional[int]:
320320
@property
321321
def block(self) -> HeadBlockData:
322322
if self._block is None:
323-
raise RuntimeError('No message from `head` channel received')
323+
raise RuntimeError('Attempt to access head block before the first message')
324324
return self._block
325325

326326
async def get_similar_contracts(self, address: str, strict: bool = False) -> List[str]:
@@ -633,87 +633,57 @@ def _default_http_config(self) -> HTTPConfig:
633633
connection_limit=25,
634634
)
635635

636-
async def _on_operation_message(self, message: List[Dict[str, Any]]) -> None:
637-
"""Parse and emit raw operations from WS"""
636+
async def _extract_message_data(self, channel: str, message: List[Any]) -> Any:
638637
for item in message:
639-
current_level = item['state']
638+
head_level = item['state']
640639
message_type = TzktMessageType(item['type'])
641-
self._logger.info('Got operation message, %s, level %s', message_type, current_level)
640+
self._logger.debug('`%s` message: %s', channel, message_type.name)
642641

643642
if message_type == TzktMessageType.STATE:
644-
self._sync_level = current_level
645-
self._level = current_level
643+
if self._sync_level != head_level:
644+
self._logger.info('Datasource level set to %s', head_level)
645+
self._sync_level = head_level
646+
self._level = head_level
646647

647648
elif message_type == TzktMessageType.DATA:
648-
self._level = current_level
649-
operations = []
650-
for operation_json in item['data']:
651-
operation = self.convert_operation(operation_json)
652-
if operation.status != 'applied':
653-
continue
654-
operations.append(operation)
655-
if operations:
656-
self.emit_operations(operations, self.block)
649+
self._level = head_level
650+
yield item['data']
657651

658652
elif message_type == TzktMessageType.REORG:
659653
if self.level is None:
660654
raise RuntimeError
661-
self.emit_rollback(self.level, current_level)
655+
self.emit_rollback(self.level, head_level)
662656

663657
else:
664658
raise NotImplementedError
665659

666-
async def _on_big_map_message(self, message: List[Dict[str, Any]]) -> None:
667-
"""Parse and emit raw big map diffs from WS"""
668-
for item in message:
669-
current_level = item['state']
670-
message_type = TzktMessageType(item['type'])
671-
self._logger.info('Got big map message, %s, level %s', message_type, current_level)
672-
673-
if message_type == TzktMessageType.STATE:
674-
self._sync_level = current_level
675-
self._level = current_level
676-
677-
elif message_type == TzktMessageType.DATA:
678-
self._level = current_level
679-
big_maps = []
680-
for big_map_json in item['data']:
681-
big_map = self.convert_big_map(big_map_json)
682-
big_maps.append(big_map)
683-
self.emit_big_maps(big_maps)
684660

685-
elif message_type == TzktMessageType.REORG:
686-
if self.level is None:
687-
raise RuntimeError
688-
self.emit_rollback(self.level, current_level)
661+
async def _on_operation_message(self, message: List[Dict[str, Any]]) -> None:
662+
"""Parse and emit raw operations from WS"""
663+
async for data in self._extract_message_data('operation', message):
664+
operations = []
665+
for operation_json in data:
666+
operation = self.convert_operation(operation_json)
667+
if operation.status != 'applied':
668+
continue
669+
operations.append(operation)
670+
if operations:
671+
self.emit_operations(operations, self.block)
689672

690-
else:
691-
raise NotImplementedError
673+
async def _on_big_map_message(self, message: List[Dict[str, Any]]) -> None:
674+
"""Parse and emit raw big map diffs from WS"""
675+
async for data in self._extract_message_data('big_map', message):
676+
big_maps = []
677+
for big_map_json in data:
678+
big_map = self.convert_big_map(big_map_json)
679+
big_maps.append(big_map)
680+
self.emit_big_maps(big_maps)
692681

693682
async def _on_head_message(self, message: List[Dict[str, Any]]) -> None:
694-
for item in message:
695-
current_level = item['state']
696-
message_type = TzktMessageType(item['type'])
697-
self._logger.info('Got block message, %s, level %s', message_type, current_level)
698-
699-
if message_type == TzktMessageType.STATE:
700-
self._sync_level = current_level
701-
self._level = current_level
702-
703-
elif message_type == TzktMessageType.DATA:
704-
self._level = current_level
705-
block_json = item['data']
706-
block = self.convert_head_block(block_json)
707-
self._block = block
708-
self.emit_head(block)
709-
710-
elif message_type == TzktMessageType.REORG:
711-
if self.level is None:
712-
raise RuntimeError
713-
self.emit_rollback(self.level, current_level)
714-
715-
else:
716-
raise NotImplementedError
683+
async for data in self._extract_message_data('head', message):
684+
block = self.convert_head_block(data)
685+
self._block = block
686+
self.emit_head(block)
717687

718688
@classmethod
719689
def convert_operation(cls, operation_json: Dict[str, Any]) -> OperationData:

src/dipdup/dipdup.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async def add_index(self, index_config: IndexConfigTemplateT) -> None:
6161
datasource_name = cast(TzktDatasourceConfig, index_config.datasource).name
6262
datasource = self._ctx.datasources[datasource_name]
6363
if not isinstance(datasource, TzktDatasource):
64-
raise RuntimeError
64+
raise RuntimeError(f'`{datasource_name}` is not a TzktDatasource')
6565
operation_index = OperationIndex(self._ctx, index_config, datasource)
6666
self._indexes[index_config.name] = operation_index
6767
await datasource.add_index(index_config)
@@ -70,7 +70,7 @@ async def add_index(self, index_config: IndexConfigTemplateT) -> None:
7070
datasource_name = cast(TzktDatasourceConfig, index_config.datasource).name
7171
datasource = self._ctx.datasources[datasource_name]
7272
if not isinstance(datasource, TzktDatasource):
73-
raise RuntimeError
73+
raise RuntimeError(f'`{datasource_name}` is not a TzktDatasource')
7474
big_map_index = BigMapIndex(self._ctx, index_config, datasource)
7575
self._indexes[index_config.name] = big_map_index
7676
await datasource.add_index(index_config)
@@ -111,22 +111,23 @@ async def dispatch_operations(self, datasource: TzktDatasource, operations: List
111111
if isinstance(index, OperationIndex) and index.datasource == datasource:
112112
index.push(level, operations, block)
113113

114-
async def dispatch_big_maps(self, datasource: TzktDatasource, big_maps: List[BigMapData]) -> None:
114+
async def dispatch_big_maps(self, datasource: TzktDatasource, big_maps: List[BigMapData], block: HeadBlockData) -> None:
115115
assert len(set(op.level for op in big_maps)) == 1
116116
level = big_maps[0].level
117117
for index in self._indexes.values():
118118
if isinstance(index, BigMapIndex) and index.datasource == datasource:
119-
index.push(level, big_maps)
119+
index.push(level, big_maps, block)
120120

121121
async def _rollback(self, datasource: TzktDatasource, from_level: int, to_level: int) -> None:
122122
logger = FormattedLogger(ROLLBACK_HANDLER)
123123
if from_level - to_level == 1:
124124
# NOTE: Single level rollbacks are processed at Index level.
125-
# NOTE: Notify all indexes with rolled back datasource to skip next level and just verify it
125+
# NOTE: Notify all indexes which use rolled back datasource to drop duplicated operations from the next block
126126
for index in self._indexes.values():
127127
if index.datasource == datasource:
128128
# NOTE: Continue to rollback with handler
129129
if not isinstance(index, OperationIndex):
130+
self._logger.info('Single level rollback is not supported by `%s` indexes', index._config.kind)
130131
break
131132
await index.single_level_rollback(from_level)
132133
else:
@@ -218,15 +219,18 @@ async def run(self, reindex: bool, oneshot: bool) -> None:
218219
async with AsyncExitStack() as stack:
219220
worker_tasks = []
220221
await stack.enter_async_context(tortoise_wrapper(url, models))
222+
223+
await self._initialize_database(reindex)
221224
for datasource in self._datasources.values():
222225
await stack.enter_async_context(datasource)
226+
227+
# NOTE: on_configure hook fires after database and datasources are initialized but before Hasura is
228+
await self._on_configure()
229+
223230
if hasura_gateway:
224231
await stack.enter_async_context(hasura_gateway)
225232
worker_tasks.append(asyncio.create_task(hasura_gateway.configure()))
226233

227-
await self._initialize_database(reindex)
228-
await self._configure()
229-
230234
self._logger.info('Starting datasources')
231235
datasource_tasks = [] if oneshot else [asyncio.create_task(d.run()) for d in self._datasources.values()]
232236

@@ -255,7 +259,7 @@ async def migrate_to_v11(self) -> None:
255259
await codegen.migrate_user_handlers_to_v11()
256260
self._finish_migration('1.1')
257261

258-
async def _configure(self) -> None:
262+
async def _on_configure(self) -> None:
259263
"""Run user-defined initial configuration handler"""
260264
configure_fn = self._config.get_configure_fn()
261265
await configure_fn(self._ctx)

src/dipdup/hasura.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ async def _get_views(self) -> List[str]:
201201
row[0]
202202
for row in (
203203
await get_connection(None).execute_query(
204-
f"SELECT table_name FROM information_schema.views WHERE table_schema = '{self._database_config.schema_name}'"
204+
f"SELECT table_name FROM information_schema.views WHERE table_schema = '{self._database_config.schema_name}' UNION "
205+
f"SELECT matviewname as table_name FROM pg_matviews WHERE schemaname = '{self._database_config.schema_name}'"
205206
)
206207
)[1]
207208
]

0 commit comments

Comments
 (0)