opentensor / btcli

Bittensor command line tool
https://docs.bittensor.com/btcli
13 stars 3 forks source link

Handle custom errors from subtensor #79

Closed thewhaleking closed 4 weeks ago

thewhaleking commented 1 month ago

Ported from https://github.com/opentensor/bittensor/pull/2305

In addition to catching the typical error messages we receive, we now also catch errors embedded in SubstrateRequestExceptions by catching these exceptions explicitly and parsing the literal dicts out of the args.

E.g. AsyncSubstrateInterface.submit_extrinsic can raise a SubstrateRequestException in two ways:

A.

responses = (
    await self._make_rpc_request(
        [
            self.make_payload(
                "rpc_request",
                "author_submitAndWatchExtrinsic",
                [str(extrinsic.data)],
            )
        ],
        result_handler=result_handler,
    )
)["rpc_request"]
response = next(
    (r for r in responses if "block_hash" in r and "extrinsic_hash" in r),
    None,
)

if not response:
    raise SubstrateRequestException(responses)

which dumps a number of dict-like responses to the SubstrateRequestException's args.

or:

B.

if "result" not in response:
    raise SubstrateRequestException(response.get("error"))

which dumps the error dict to the SubstrateRequestException's args.

We handle this in both cases by parsing the SubstrateRequestException.args:

if isinstance(error_message, Exception):
    # generally gotten through SubstrateRequestException args
    new_error_message = None
    for arg in error_message.args:
        try:
            d = ast.literal_eval(arg)
            if isinstance(d, dict):
                if "error" in d:  # case A
                    new_error_message = d["error"]
                    break
                elif all(x in d for x in ["code", "message", "data"]):  # case B
                    new_error_message = d
                    break
        except ValueError:
            pass
    if new_error_message is None:  # case C
        return_val = " ".join(error_message.args)
        return f"Subtensor returned: {return_val}"
    else:
        error_message = new_error_message

This also allows us to handle the majority of raised SubstrateRequestException args which have a single string with the message included (case C).