Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle custom errors from subtensor #79

Merged
8 changes: 6 additions & 2 deletions bittensor_cli/src/bittensor/extrinsics/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,10 @@ async def get_neuron_for_pubkey_and_subnet():
if not await response.is_success:
success, err_msg = (
False,
format_error_message(await response.error_message),
format_error_message(
await response.error_message,
substrate=subtensor.substrate,
),
)

if not success:
Expand Down Expand Up @@ -785,7 +788,8 @@ async def run_faucet_extrinsic(
await response.process_events()
if not await response.is_success:
err_console.print(
f":cross_mark: [red]Failed[/red]: {format_error_message(await response.error_message)}"
f":cross_mark: [red]Failed[/red]: "
f"{format_error_message(await response.error_message, subtensor.substrate)}"
)
if attempts == max_allowed_attempts:
raise MaxAttemptsException
Expand Down
14 changes: 8 additions & 6 deletions bittensor_cli/src/bittensor/extrinsics/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,23 @@
import time
from typing import Union, List, TYPE_CHECKING

from bittensor_wallet import Wallet
from bittensor_wallet import Wallet, Keypair
from bittensor_wallet.errors import KeyFileError
import numpy as np
from numpy.typing import NDArray
from rich.prompt import Confirm
from rich.table import Table, Column
from scalecodec import ScaleBytes, U16, Vec
from substrateinterface.exceptions import SubstrateRequestException

from bittensor_wallet import Keypair
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
from bittensor_cli.src.bittensor.extrinsics.registration import is_hotkey_registered
from bittensor_cli.src.bittensor.utils import (
console,
err_console,
u16_normalized_float,
print_verbose,
format_error_message,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -481,7 +482,6 @@ async def _do_set_weights():
)

success, error_message = await _do_set_weights()
console.print(success, error_message)

if not wait_for_finalization and not wait_for_inclusion:
return True
Expand All @@ -490,9 +490,11 @@ async def _do_set_weights():
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
return True
else:
err_console.print(f":cross_mark: [red]Failed[/red]: {error_message}")
fmt_err = format_error_message(error_message, subtensor.substrate)
err_console.print(f":cross_mark: [red]Failed[/red]: {fmt_err}")
return False

except Exception as e:
err_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e))
except SubstrateRequestException as e:
fmt_err = format_error_message(e, subtensor.substrate)
err_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(fmt_err))
return False
5 changes: 3 additions & 2 deletions bittensor_cli/src/bittensor/extrinsics/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from bittensor_wallet import Wallet
from bittensor_wallet.errors import KeyFileError
from rich.prompt import Confirm
from substrateinterface.exceptions import SubstrateRequestException

from bittensor_cli.src import NETWORK_EXPLORER_MAP
from bittensor_cli.src.bittensor.balances import Balance
Expand Down Expand Up @@ -59,11 +60,11 @@ async def get_transfer_fee() -> Balance:
payment_info = await subtensor.substrate.get_payment_info(
call=call, keypair=wallet.coldkeypub
)
except Exception as e:
except SubstrateRequestException as e:
payment_info = {"partialFee": int(2e7)} # assume 0.02 Tao
err_console.print(
f":cross_mark: [red]Failed to get payment info[/red]:[bold white]\n"
f" {e}[/bold white]\n"
f" {format_error_message(e, subtensor.substrate)}[/bold white]\n"
f" Defaulting to default transfer fee: {payment_info['partialFee']}"
)

Expand Down
8 changes: 5 additions & 3 deletions bittensor_cli/src/bittensor/subtensor_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,9 +927,11 @@ async def sign_and_send_extrinsic(
if await response.is_success:
return True, ""
else:
return False, format_error_message(await response.error_message)
return False, format_error_message(
await response.error_message, substrate=self.substrate
)
except SubstrateRequestException as e:
return False, e
return False, format_error_message(e, substrate=self.substrate)

async def get_children(self, hotkey, netuid) -> tuple[bool, list, str]:
"""
Expand Down Expand Up @@ -959,7 +961,7 @@ async def get_children(self, hotkey, netuid) -> tuple[bool, list, str]:
else:
return True, [], ""
except SubstrateRequestException as e:
return False, [], str(e)
return False, [], format_error_message(e, self.substrate)

async def get_subnet_hyperparameters(
self, netuid: int, block_hash: Optional[str] = None
Expand Down
87 changes: 77 additions & 10 deletions bittensor_cli/src/bittensor/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ast
import math
import os
import sqlite3
Expand Down Expand Up @@ -26,6 +27,9 @@

if TYPE_CHECKING:
from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
from bittensor_cli.src.bittensor.async_substrate_interface import (
AsyncSubstrateInterface,
)

console = Console()
err_console = Console(stderr=True)
Expand Down Expand Up @@ -448,24 +452,87 @@ def get_explorer_url_for_network(
return explorer_urls


def format_error_message(error_message: dict) -> str:
def format_error_message(
error_message: Union[dict, Exception], substrate: "AsyncSubstrateInterface"
) -> str:
"""
Formats an error message from the Subtensor error information to using in extrinsics.
Formats an error message from the Subtensor error information for use in extrinsics.

:param error_message: A dictionary containing the error information from Subtensor.
Args:
error_message: A dictionary containing the error information from Subtensor, or a SubstrateRequestException
containing dictionary literal args.
substrate: The initialised SubstrateInterface object to use.

:return: A formatted error message string.
Returns:
str: A formatted error message string.
"""
err_type = "UnknownType"
err_name = "UnknownError"
err_type = "UnknownType"
err_description = "Unknown Description"

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:
new_error_message = d["error"]
break
elif all(x in d for x in ["code", "message", "data"]):
new_error_message = d
break
except ValueError:
pass
if new_error_message is None:
return_val = " ".join(error_message.args)
return f"Subtensor returned: {return_val}"
else:
error_message = new_error_message

if isinstance(error_message, dict):
err_type = error_message.get("type", err_type)
err_name = error_message.get("name", err_name)
err_docs = error_message.get("docs", [])
err_description = err_docs[0] if len(err_docs) > 0 else err_description
return f"Subtensor returned `{err_name} ({err_type})` error. This means: `{err_description}`"
# subtensor error structure
if (
error_message.get("code")
and error_message.get("message")
and error_message.get("data")
):
err_name = "SubstrateRequestException"
err_type = error_message.get("message", "")
err_data = error_message.get("data", "")

# subtensor custom error marker
if err_data.startswith("Custom error:") and substrate:
if substrate.metadata:
try:
pallet = substrate.metadata.get_metadata_pallet(
"SubtensorModule"
)
error_index = int(err_data.split("Custom error:")[-1])

error_dict = pallet.errors[error_index].value
err_type = error_dict.get("message", err_type)
err_docs = error_dict.get("docs", [])
err_description = err_docs[0] if err_docs else err_description
except (AttributeError, IndexError):
err_console.print(
"Substrate pallets data unavailable. This is usually caused by an uninitialized substrate."
)
else:
err_description = err_data

elif (
error_message.get("type")
and error_message.get("name")
and error_message.get("docs")
):
err_type = error_message.get("type", err_type)
err_name = error_message.get("name", err_name)
err_docs = error_message.get("docs", [err_description])
err_description = err_docs[0] if err_docs else err_description

return f"Subtensor returned `{err_name}({err_type})` error. This means: `{err_description}`."


def convert_blocks_to_time(blocks: int, block_time: int = 12) -> tuple[int, int, int]:
Expand Down
12 changes: 9 additions & 3 deletions bittensor_cli/src/commands/stake/children_hotkeys.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
u16_to_float,
u64_to_float,
is_valid_ss58_address,
format_error_message,
)


Expand Down Expand Up @@ -208,8 +209,11 @@ async def set_childkey_take_extrinsic(
# )
return False, error_message

except Exception as e:
return False, f"Exception occurred while setting childkey take: {str(e)}"
except SubstrateRequestException as e:
return (
False,
f"Exception occurred while setting childkey take: {format_error_message(e, subtensor.substrate)}",
)


async def get_childkey_take(subtensor, hotkey: str, netuid: int) -> Optional[int]:
Expand All @@ -232,7 +236,9 @@ async def get_childkey_take(subtensor, hotkey: str, netuid: int) -> Optional[int
return int(childkey_take_.value)

except SubstrateRequestException as e:
err_console.print(f"Error querying ChildKeys: {e}")
err_console.print(
f"Error querying ChildKeys: {format_error_message(e, subtensor.substrate)}"
)
return None


Expand Down
2 changes: 1 addition & 1 deletion bittensor_cli/src/commands/subnets.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async def _find_event_attributes_in_extrinsic_receipt(
await response.process_events()
if not await response.is_success:
err_console.print(
f":cross_mark: [red]Failed[/red]: {format_error_message(await response.error_message)}"
f":cross_mark: [red]Failed[/red]: {format_error_message(await response.error_message, substrate)}"
)
await asyncio.sleep(0.5)
return False
Expand Down
Loading
Loading