diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5e5020b79e..2b4763c077 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -38,6 +38,7 @@ ) from bittensor.core.extrinsics.async_serving import serve_axon_extrinsic from bittensor.core.extrinsics.async_unstaking import unstake_extrinsic +from bittensor.core.extrinsics.async_commit_reveal import commit_reveal_v3_extrinsic from bittensor.core.settings import ( TYPE_REGISTRY, DEFAULTS, @@ -1911,8 +1912,15 @@ async def set_weights( """ if (await self.commit_reveal_enabled(netuid=netuid)) is True: # go with `commit reveal v3` extrinsic - raise NotImplementedError( - "Not implemented yet for AsyncSubtensor. Coming soon." + return await commit_reveal_v3_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + uids=uids, + weights=weights, + version_key=version_key, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) else: # go with classic `set weights extrinsic` diff --git a/bittensor/core/extrinsics/async_commit_reveal.py b/bittensor/core/extrinsics/async_commit_reveal.py new file mode 100644 index 0000000000..efe46f28d7 --- /dev/null +++ b/bittensor/core/extrinsics/async_commit_reveal.py @@ -0,0 +1,157 @@ +from typing import Optional, Union, TYPE_CHECKING + +import numpy as np +from bittensor_commit_reveal import get_encrypted_commit +from numpy.typing import NDArray + +from bittensor.core.extrinsics.utils import async_submit_extrinsic +from bittensor.core.settings import version_as_int +from bittensor.utils import format_error_message +from bittensor.utils.btlogging import logging +from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit + +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.core.async_subtensor import AsyncSubtensor + from bittensor.utils.registration import torch + + +async def _do_commit_reveal_v3( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + commit: bytes, + reveal_round: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, +) -> tuple[bool, Optional[str]]: + """ + Executes the commit-reveal phase 3 for a given netuid and commit, and optionally waits for extrinsic inclusion or + finalization. + + Arguments: + wallet: Wallet An instance of the Wallet class containing the user's keypair. + netuid: int The network unique identifier. + commit bytes The commit data in bytes format. + reveal_round: int The round number for the reveal phase. + wait_for_inclusion: bool, optional Flag indicating whether to wait for the extrinsic to be included in a block. + wait_for_finalization: bool, optional Flag indicating whether to wait for the extrinsic to be finalized. + + Returns: + A tuple where the first element is a boolean indicating success or failure, and the second element is an + optional string containing error message if any. + """ + logging.info( + f"Committing weights hash [blue]{commit.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with " + f"reveal round [blue]{reveal_round}[/blue]..." + ) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_crv3_weights", + call_params={ + "netuid": netuid, + "commit": commit, + "reveal_round": reveal_round, + }, + ) + extrinsic = await subtensor.substrate.create_signed_extrinsic( + call=call, + keypair=wallet.hotkey, + ) + + response = await async_submit_extrinsic( + subtensor=subtensor, + extrinsic=extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return True, "Not waiting for finalization or inclusion." + + if response.is_success: + return True, None + else: + return False, format_error_message(response.error_message) + + +async def commit_reveal_v3_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + uids: Union[NDArray[np.int64], "torch.LongTensor", list], + weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + version_key: int = version_as_int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, +) -> tuple[bool, str]: + """ + Commits and reveals weights for given subtensor and wallet with provided uids and weights. + + Arguments: + subtensor: The AsyncSubtensor instance. + wallet: The wallet to use for committing and revealing. + netuid: The id of the network. + uids: The uids to commit. + weights: The weights associated with the uids. + version_key: The version key to use for committing and revealing. Default is version_as_int. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. Default is False. + wait_for_finalization: Whether to wait for the finalization of the transaction. Default is False. + + Returns: + A tuple where the first element is a boolean indicating success or failure, and the second element is a message + associated with the result. + """ + try: + # Convert uids and weights + if isinstance(uids, list): + uids = np.array(uids, dtype=np.int64) + if isinstance(weights, list): + weights = np.array(weights, dtype=np.float32) + + # Reformat and normalize. + uids, weights = convert_weights_and_uids_for_emit(uids, weights) + + current_block = await subtensor.get_current_block() + subnet_hyperparameters = await subtensor.get_subnet_hyperparameters( + netuid, block=current_block + ) + tempo = subnet_hyperparameters.tempo + subnet_reveal_period_epochs = ( + subnet_hyperparameters.commit_reveal_weights_interval + ) + + # Encrypt `commit_hash` with t-lock and `get reveal_round` + commit_for_reveal, reveal_round = get_encrypted_commit( + uids=uids, + weights=weights, + version_key=version_key, + tempo=tempo, + current_block=current_block, + netuid=netuid, + subnet_reveal_period_epochs=subnet_reveal_period_epochs, + ) + + success, message = await _do_commit_reveal_v3( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + commit=commit_for_reveal, + reveal_round=reveal_round, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if success is True: + logging.success( + f"[green]Finalized![/green] Weights commited with reveal round [blue]{reveal_round}[/blue]." + ) + return True, f"reveal_round:{reveal_round}" + else: + logging.error(message) + return False, message + + except Exception as e: + logging.error(f":cross_mark: [red]Failed. Error:[/red] {e}") + return False, str(e)