Skip to content

Smoldot connection handling #426

@rosesopranodesertbat

Description

@rosesopranodesertbat

I switched to using Smoldot as looks like a lot of the RPC providers are shutting down. However noticed that a lot of queries are failing because of connection shutdowns. I vibe-coded a fix to reconnect/retry on failure but think it should probably be upstream.

class SafeSubstrate:
    def __init__(
        self,
        connection: SubstrateInterface,
        label: str,
        reconnect_fn: Optional[Callable[[], SubstrateInterface]] = None
    ) -> None:
        self._connection = connection
        self._label = label
        self._reconnect_fn = reconnect_fn

    def query(self, module: str, storage_function: str, params: Optional[List[Any]] = None, block_hash: Optional[str] = None):
        retries = int(os.environ.get('SMOLDOT_QUERY_RETRIES', '5'))
        base_delay = float(os.environ.get('SMOLDOT_QUERY_BASE_DELAY', '0.5'))
        params = params or []

        for attempt in range(1, retries + 1):
            try:
                return self._connection.query(module, storage_function, params=params, block_hash=block_hash)
            except SubstrateRequestException as exc:
                if not is_transient_smoldot_error(exc) or attempt == retries:
                    raise
                delay = base_delay * (2 ** (attempt - 1))
                name = f"{self._label}.{module}.{storage_function}" if self._label else f"{module}.{storage_function}"
                logger.warning(f"{name} failed with smoldot connection error (attempt {attempt}/{retries}); retrying in {delay:.2f}s")
                if self._reconnect_fn is not None:
                    self._connection = self._reconnect_fn()
                time.sleep(delay)

        return self._connection.query(module, storage_function, params=params, block_hash=block_hash)

    def __getattr__(self, name: str):
        return getattr(self._connection, name)

def is_transient_smoldot_error(exc: SubstrateRequestException) -> bool:
    error = exc.args[0] if exc.args else None
    if isinstance(error, dict):
        message = str(error.get('message', ''))
        code = error.get('code')
    else:
        message = str(exc)
        code = None
    if code == -32000 and ('NoConnection' in message or 'ConnectionShutdown' in message):
        return True
    return 'NoConnection' in message or 'ConnectionShutdown' in message

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions