import asyncio import logging import traceback from asyncio import CancelledError from typing import Optional from sqlalchemy import orm from web3 import Web3 from web3.eth import AsyncEth from mev_inspect.block import create_from_block_number from mev_inspect.classifiers.trace import TraceClassifier from mev_inspect.inspect_block import inspect_block, inspect_many_blocks from mev_inspect.methods import get_block_receipts, trace_block from mev_inspect.provider import get_base_provider logger = logging.getLogger(__name__) # add missing parity methods # this is a bit gross AsyncEth.trace_block = trace_block AsyncEth.get_block_receipts = get_block_receipts class MEVInspector: def __init__( self, rpc: str, max_concurrency: int = 1, request_timeout: int = 300, ): base_provider = get_base_provider(rpc, request_timeout=request_timeout) self.w3 = Web3(base_provider, modules={"eth": (AsyncEth,)}, middlewares=[]) self.trace_classifier = TraceClassifier() self.max_concurrency = asyncio.Semaphore(max_concurrency) async def create_from_block( self, trace_db_session: Optional[orm.Session], block_number: int, ): return await create_from_block_number( w3=self.w3, block_number=block_number, trace_db_session=trace_db_session, ) async def inspect_single_block( self, inspect_db_session: orm.Session, block: int, trace_db_session: Optional[orm.Session], ): return await inspect_block( inspect_db_session, self.w3, self.trace_classifier, block, trace_db_session=trace_db_session, ) async def inspect_many_blocks( self, inspect_db_session: orm.Session, trace_db_session: Optional[orm.Session], after_block: int, before_block: int, block_batch_size: int = 10, ): tasks = [] for block_number in range(after_block, before_block, block_batch_size): batch_after_block = block_number batch_before_block = min(block_number + block_batch_size, before_block) tasks.append( asyncio.ensure_future( self.safe_inspect_many_blocks( inspect_db_session, trace_db_session, after_block_number=batch_after_block, before_block_number=batch_before_block, ) ) ) logger.info(f"Gathered {before_block-after_block} blocks to inspect") try: await asyncio.gather(*tasks) except CancelledError: logger.info("Requested to exit, cleaning up...") except Exception as e: logger.error(f"Exited due to {type(e)}") traceback.print_exc() raise async def safe_inspect_many_blocks( self, inspect_db_session: orm.Session, trace_db_session: Optional[orm.Session], after_block_number: int, before_block_number: int, ): async with self.max_concurrency: return await inspect_many_blocks( inspect_db_session, self.w3, self.trace_classifier, after_block_number, before_block_number, trace_db_session=trace_db_session, )