async based middleware, better logging and async requests
This commit is contained in:
		
							
								
								
									
										26
									
								
								cli.py
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								cli.py
									
									
									
									
									
								
							@@ -1,7 +1,10 @@
 | 
				
			|||||||
import asyncio
 | 
					import asyncio
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					from asyncio import CancelledError
 | 
				
			||||||
from functools import wraps
 | 
					from functools import wraps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import click
 | 
					import click
 | 
				
			||||||
@@ -12,7 +15,6 @@ from mev_inspect.classifiers.trace import TraceClassifier
 | 
				
			|||||||
from mev_inspect.db import get_inspect_session, get_trace_session
 | 
					from mev_inspect.db import get_inspect_session, get_trace_session
 | 
				
			||||||
from mev_inspect.inspect_block import inspect_block
 | 
					from mev_inspect.inspect_block import inspect_block
 | 
				
			||||||
from mev_inspect.provider import get_base_provider
 | 
					from mev_inspect.provider import get_base_provider
 | 
				
			||||||
from mev_inspect.retry import http_retry_with_backoff_request_middleware
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
RPC_URL_ENV = "RPC_URL"
 | 
					RPC_URL_ENV = "RPC_URL"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,11 +33,17 @@ def coro(f):
 | 
				
			|||||||
    @wraps(f)
 | 
					    @wraps(f)
 | 
				
			||||||
    def wrapper(*args, **kwargs):
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
        loop = asyncio.get_event_loop()
 | 
					        loop = asyncio.get_event_loop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def cancel_task_callback():
 | 
				
			||||||
 | 
					            for task in asyncio.all_tasks():
 | 
				
			||||||
 | 
					                task.cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for sig in (signal.SIGINT, signal.SIGTERM):
 | 
				
			||||||
 | 
					            loop.add_signal_handler(sig, cancel_task_callback)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            loop.run_until_complete(f(*args, **kwargs))
 | 
					            loop.run_until_complete(f(*args, **kwargs))
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            loop.run_until_complete(loop.shutdown_asyncgens())
 | 
					            loop.run_until_complete(loop.shutdown_asyncgens())
 | 
				
			||||||
            loop.close()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return wrapper
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,11 +103,7 @@ async def inspect_many_blocks_command(
 | 
				
			|||||||
    trace_db_session = get_trace_session()
 | 
					    trace_db_session = get_trace_session()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    base_provider = get_base_provider(rpc, request_timeout=request_timeout)
 | 
					    base_provider = get_base_provider(rpc, request_timeout=request_timeout)
 | 
				
			||||||
    w3 = Web3(
 | 
					    w3 = Web3(base_provider, modules={"eth": (AsyncEth,)}, middlewares=[])
 | 
				
			||||||
        base_provider,
 | 
					 | 
				
			||||||
        modules={"eth": (AsyncEth,)},
 | 
					 | 
				
			||||||
        middlewares=[http_retry_with_backoff_request_middleware],
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    trace_classifier = TraceClassifier()
 | 
					    trace_classifier = TraceClassifier()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,7 +126,13 @@ async def inspect_many_blocks_command(
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    logger.info(f"Gathered {len(tasks)} blocks to inspect")
 | 
					    logger.info(f"Gathered {len(tasks)} blocks to inspect")
 | 
				
			||||||
    await asyncio.gather(*tasks)
 | 
					    try:
 | 
				
			||||||
 | 
					        await asyncio.gather(*tasks)
 | 
				
			||||||
 | 
					    except CancelledError:
 | 
				
			||||||
 | 
					        logger.info("Requested to exit, cleaning up...")
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(f"Existed due to {type(e)}")
 | 
				
			||||||
 | 
					        traceback.print_exc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def safe_inspect_block(
 | 
					async def safe_inspect_block(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import asyncio
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from typing import List, Optional
 | 
					from typing import List, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,16 +40,17 @@ async def _fetch_block(
 | 
				
			|||||||
    base_provider,
 | 
					    base_provider,
 | 
				
			||||||
    block_number: int,
 | 
					    block_number: int,
 | 
				
			||||||
) -> Block:
 | 
					) -> Block:
 | 
				
			||||||
    block_json = await w3.eth.get_block(block_number)
 | 
					    block_json, receipts_json, traces_json, base_fee_per_gas = await asyncio.gather(
 | 
				
			||||||
    receipts_json = await base_provider.make_request(
 | 
					        w3.eth.get_block(block_number),
 | 
				
			||||||
        "eth_getBlockReceipts", [block_number]
 | 
					        base_provider.make_request("eth_getBlockReceipts", [block_number]),
 | 
				
			||||||
 | 
					        base_provider.make_request("trace_block", [block_number]),
 | 
				
			||||||
 | 
					        fetch_base_fee_per_gas(w3, block_number),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    traces_json = await base_provider.make_request("trace_block", [block_number])
 | 
					
 | 
				
			||||||
    receipts: List[Receipt] = [
 | 
					    receipts: List[Receipt] = [
 | 
				
			||||||
        Receipt(**receipt) for receipt in receipts_json["result"]
 | 
					        Receipt(**receipt) for receipt in receipts_json["result"]
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    traces = [Trace(**trace_json) for trace_json in traces_json["result"]]
 | 
					    traces = [Trace(**trace_json) for trace_json in traces_json["result"]]
 | 
				
			||||||
    base_fee_per_gas = await fetch_base_fee_per_gas(w3, block_number)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Block(
 | 
					    return Block(
 | 
				
			||||||
        block_number=block_number,
 | 
					        block_number=block_number,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,9 @@
 | 
				
			|||||||
from web3 import Web3, AsyncHTTPProvider
 | 
					from web3 import Web3, AsyncHTTPProvider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from mev_inspect.retry import http_retry_with_backoff_request_middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_base_provider(rpc: str, request_timeout: int = 500) -> Web3.AsyncHTTPProvider:
 | 
					def get_base_provider(rpc: str, request_timeout: int = 500) -> Web3.AsyncHTTPProvider:
 | 
				
			||||||
    base_provider = AsyncHTTPProvider(rpc, request_kwargs={"timeout": request_timeout})
 | 
					    base_provider = AsyncHTTPProvider(rpc, request_kwargs={"timeout": request_timeout})
 | 
				
			||||||
 | 
					    base_provider.middlewares += (http_retry_with_backoff_request_middleware,)
 | 
				
			||||||
    return base_provider
 | 
					    return base_provider
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,15 @@
 | 
				
			|||||||
import time
 | 
					import asyncio
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    Any,
 | 
					    Any,
 | 
				
			||||||
    Callable,
 | 
					    Callable,
 | 
				
			||||||
    Collection,
 | 
					    Collection,
 | 
				
			||||||
    Type,
 | 
					    Type,
 | 
				
			||||||
 | 
					    Coroutine,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from asyncio.exceptions import TimeoutError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from requests.exceptions import (
 | 
					from requests.exceptions import (
 | 
				
			||||||
    ConnectionError,
 | 
					    ConnectionError,
 | 
				
			||||||
@@ -20,40 +25,54 @@ from web3.types import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.basicConfig(stream=sys.stdout, level=logging.INFO)
 | 
				
			||||||
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def exception_retry_with_backoff_middleware(
 | 
					async def exception_retry_with_backoff_middleware(
 | 
				
			||||||
    make_request: Callable[[RPCEndpoint, Any], RPCResponse],
 | 
					    make_request: Callable[[RPCEndpoint, Any], Any],
 | 
				
			||||||
    web3: Web3,  # pylint: disable=unused-argument
 | 
					    web3: Web3,  # pylint: disable=unused-argument
 | 
				
			||||||
    errors: Collection[Type[BaseException]],
 | 
					    errors: Collection[Type[BaseException]],
 | 
				
			||||||
    retries: int = 5,
 | 
					    retries: int = 5,
 | 
				
			||||||
    backoff_time_seconds: float = 0.1,
 | 
					    backoff_time_seconds: float = 0.1,
 | 
				
			||||||
) -> Callable[[RPCEndpoint, Any], RPCResponse]:
 | 
					) -> Callable[[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]]:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Creates middleware that retries failed HTTP requests. Is a default
 | 
					    Creates middleware that retries failed HTTP requests. Is a default
 | 
				
			||||||
    middleware for HTTPProvider.
 | 
					    middleware for HTTPProvider.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
 | 
					    async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if check_if_retry_on_failure(method):
 | 
					        if check_if_retry_on_failure(method):
 | 
				
			||||||
            for i in range(retries):
 | 
					            for i in range(retries):
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    return make_request(method, params)
 | 
					                    return await make_request(method, params)
 | 
				
			||||||
                # https://github.com/python/mypy/issues/5349
 | 
					                # https://github.com/python/mypy/issues/5349
 | 
				
			||||||
                except errors:  # type: ignore
 | 
					                except errors:  # type: ignore
 | 
				
			||||||
 | 
					                    logger.error(
 | 
				
			||||||
 | 
					                        f"Request for method {method}, block: {int(params[0], 16)}, retrying: {i}/{retries}"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    if i < retries - 1:
 | 
					                    if i < retries - 1:
 | 
				
			||||||
                        time.sleep(backoff_time_seconds)
 | 
					                        backoff_time = backoff_time_seconds * (
 | 
				
			||||||
 | 
					                            random.uniform(5, 10) ** i
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        await asyncio.sleep(backoff_time)
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        raise
 | 
					                        raise
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return make_request(method, params)
 | 
					            return await make_request(method, params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return middleware
 | 
					    return middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def http_retry_with_backoff_request_middleware(
 | 
					async def http_retry_with_backoff_request_middleware(
 | 
				
			||||||
    make_request: Callable[[RPCEndpoint, Any], Any], web3: Web3
 | 
					    make_request: Callable[[RPCEndpoint, Any], Any], web3: Web3
 | 
				
			||||||
) -> Callable[[RPCEndpoint, Any], Any]:
 | 
					) -> Callable[[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]]:
 | 
				
			||||||
    return await exception_retry_with_backoff_middleware(
 | 
					    return await exception_retry_with_backoff_middleware(
 | 
				
			||||||
        make_request, web3, (ConnectionError, HTTPError, Timeout, TooManyRedirects)
 | 
					        make_request,
 | 
				
			||||||
 | 
					        web3,
 | 
				
			||||||
 | 
					        (ConnectionError, HTTPError, Timeout, TooManyRedirects, TimeoutError),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user