diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index cfb02af0aa..d8e5111e3f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5647,6 +5647,10 @@ async def compose_call( Returns: GenericCall: Composed call object ready for extrinsic submission. + + Notes: + For detailed documentation and examples of composing calls, including the CallBuilder utility, see: + """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) @@ -6645,14 +6649,14 @@ async def mev_submit_encrypted( self, wallet: "Wallet", call: "GenericCall", - signer_keypair: Optional["Keypair"] = None, + sign_with: str = "coldkey", *, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, wait_for_revealed_execution: bool = True, - blocks_for_revealed_execution: int = 5, + blocks_for_revealed_execution: int = 3, ) -> ExtrinsicResponse: """ Submits an encrypted extrinsic to the MEV Shield pallet. @@ -6663,17 +6667,17 @@ async def mev_submit_encrypted( Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). call: The GenericCall object to encrypt and submit. - signer_keypair: The keypair used to sign the inner call. + sign_with: The keypair to use for signing the inner call/extrinsic. Can be either "coldkey" or "hotkey". period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. - wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators + wait_for_revealed_execution: Whether to wait for the executed event, indicating that validators have successfully decrypted and executed the inner call. If True, the function will poll subsequent blocks for the event matching this submission's commitment. - blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after + blocks_for_revealed_execution: Maximum number of blocks to poll for the executed event after inclusion. The function checks blocks from start_block+1 to start_block + blocks_for_revealed_execution. Returns immediately if the event is found before the block limit is reached. @@ -6689,12 +6693,19 @@ async def mev_submit_encrypted( payload_core = signer_bytes (32B) + nonce (u32 LE, 4B) + SCALE(call) plaintext = payload_core + b"\\x01" + signature (64B for sr25519) commitment = blake2_256(payload_core) + + Notes: + For detailed documentation and examples of MEV Shield protection, see: + + + For creating GenericCall objects to use with this method, see: + """ return await submit_encrypted_extrinsic( subtensor=self, wallet=wallet, call=call, - signer_keypair=signer_keypair, + sign_with=sign_with, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 49d2852254..f2e988b30e 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -43,6 +43,7 @@ async def set_children_extrinsic( raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -129,6 +130,7 @@ async def root_set_pending_childkey_cooldown_extrinsic( raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. diff --git a/bittensor/core/extrinsics/asyncex/mev_shield.py b/bittensor/core/extrinsics/asyncex/mev_shield.py index 40feffeb23..b60f4ddad9 100644 --- a/bittensor/core/extrinsics/asyncex/mev_shield.py +++ b/bittensor/core/extrinsics/asyncex/mev_shield.py @@ -6,92 +6,104 @@ from bittensor.core.extrinsics.pallets import MevShield from bittensor.core.extrinsics.utils import ( - get_event_attributes_by_event_name, get_mev_commitment_and_ciphertext, - post_process_mev_response, - POST_SUBMIT_MEV_EVENTS, - MEV_SUBMITTED_EVENT, + get_event_data_by_event_name, ) from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import format_error_message from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor.core.async_subtensor import AsyncSubtensor - from bittensor_wallet import Wallet, Keypair + from bittensor_wallet import Wallet from scalecodec.types import GenericCall -async def find_revealed_extrinsic( +async def wait_for_extrinsic_by_hash( subtensor: "AsyncSubtensor", - event_names: list[str], - event_hash_id: str, - start_block_hash: str, - blocks_ahead: int = 5, -) -> tuple[str, "AsyncExtrinsicReceipt"] | tuple[None, None]: + extrinsic_hash: str, + shield_id: str, + submit_block_hash: str, + timeout_blocks: int = 3, +) -> Optional["AsyncExtrinsicReceipt"]: """ - Searches for an extrinsic containing a specific MEV Shield event in subsequent blocks. + Wait for the result of a MeV Shield encrypted extrinsic. - This function iterates through blocks starting from the specified block hash and searches for extrinsics that - contain a MEV Shield event (DecryptedExecuted or DecryptedRejected) matching the provided wrapper_id and signer. It - checks each extrinsic's triggered events to find the matching event. + After submit_encrypted succeeds, the block author will decrypt and submit the inner extrinsic directly. This + function polls subsequent blocks looking for either: + - an extrinsic matching the provided hash (success) + OR + - a markDecryptionFailed extrinsic with matching shield ID (failure) - Parameters: - subtensor: The Subtensor instance used for blockchain queries. - event_names: The event identifiers to search for. Typically "DecryptedExecuted" or "DecryptedRejected" for MEV - Shield transactions. - event_hash_id: The wrapper_id (hash of (author, commitment, ciphertext)) to match. This uniquely identifies a - specific MEV Shield submission. - start_block_hash: The hash of the block where the search should begin. Usually the block where submit_encrypted - was included. - blocks_ahead: Maximum number of blocks to search ahead from the start block. Defaults to 5 blocks. The function - will check blocks from start_block + 1 to start_block + blocks_ahead (the start block itself is not checked, - as execute_revealed will be in subsequent blocks). + Args: + subtensor: SubtensorInterface instance. + extrinsic_hash: The hash of the inner extrinsic to find. + shield_id: The wrapper ID from EncryptedSubmitted event (for detecting decryption failures). + submit_block_hash: Block hash where submit_encrypted was included. + timeout_blocks: Max blocks to wait (default 3). Returns: - Tuple with event name and ExtrinsicReceipt object. + Optional ExtrinsicReceipt. """ - start_block_number = await subtensor.substrate.get_block_number(start_block_hash) - for offset in range(1, blocks_ahead + 1): - current_block_number = start_block_number + offset + starting_block = await subtensor.substrate.get_block_number(submit_block_hash) + current_block = starting_block + 1 - try: - current_block_hash = await subtensor.substrate.get_block_hash( - current_block_number - ) - events = await subtensor.substrate.get_events(current_block_hash) - except Exception as e: - logging.debug( - f"Error getting extrinsics for block `{current_block_number}`: {e}" - ) - continue - - for event_name in event_names: - if event := get_event_attributes_by_event_name(events, event_name): - if event["attributes"]["id"] == event_hash_id: - return event_name, AsyncExtrinsicReceipt( - substrate=subtensor.substrate, - block_hash=current_block_hash, - extrinsic_idx=event["extrinsic_idx"], - ) + while current_block - starting_block <= timeout_blocks: + logging.debug( + f"Waiting for MEV Protection (checking block {current_block - starting_block} of {timeout_blocks})..." + ) - logging.debug(f"No {event_names} event found in block {current_block_number}.") await subtensor.wait_for_block() - return None, None + block_hash = await subtensor.substrate.get_block_hash(current_block) + extrinsics = await subtensor.substrate.get_extrinsics(block_hash) + + result_idx = None + for idx, extrinsic in enumerate(extrinsics): + # Success: Inner extrinsic executed + if f"0x{extrinsic.extrinsic_hash.hex()}" == extrinsic_hash: + result_idx = idx + break + + # Failure: Decryption failed + call = extrinsic.value.get("call", {}) + if ( + call.get("call_module") == "MevShield" + and call.get("call_function") == "mark_decryption_failed" + ): + call_args = call.get("call_args", []) + for arg in call_args: + if arg.get("name") == "id" and arg.get("value") == shield_id: + result_idx = idx + break + if result_idx is not None: + break + + if result_idx is not None: + return AsyncExtrinsicReceipt( + substrate=subtensor.substrate, + block_hash=block_hash, + block_number=current_block, + extrinsic_idx=result_idx, + ) + + current_block += 1 + + return None async def submit_encrypted_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", call: "GenericCall", - signer_keypair: Optional["Keypair"] = None, + sign_with: str = "coldkey", period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, wait_for_revealed_execution: bool = True, - blocks_for_revealed_execution: int = 5, + blocks_for_revealed_execution: int = 3, ) -> ExtrinsicResponse: """ Submits an encrypted extrinsic to the MEV Shield pallet. @@ -103,17 +115,17 @@ async def submit_encrypted_extrinsic( subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). call: The GenericCall object to encrypt and submit. - signer_keypair: The Keypair object used for signing the inner call. + sign_with: The keypair to use for signing the inner call/extrinsic. Can be either "coldkey" or "hotkey". period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. - wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators have + wait_for_revealed_execution: Whether to wait for the executed event, indicating that validators have successfully decrypted and executed the inner call. If True, the function will poll subsequent blocks for the event matching this submission's commitment. - blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after inclusion. + blocks_for_revealed_execution: Maximum number of blocks to poll for the executed event after inclusion. The function checks blocks from start_block + 1 to start_block + blocks_for_revealed_execution. Returns immediately if the event is found before the block limit is reached. @@ -134,10 +146,12 @@ async def submit_encrypted_extrinsic( protection. """ try: - if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) - ).success: - return unlocked + if sign_with not in ("coldkey", "hotkey"): + raise AttributeError( + f"'sign_with' must be either 'coldkey' or 'hotkey', not '{sign_with}'" + ) + + unlock_type = "both" if sign_with == "hotkey" else "coldkey" if wait_for_revealed_execution and not wait_for_inclusion: return ExtrinsicResponse.from_exception( @@ -147,9 +161,12 @@ async def submit_encrypted_extrinsic( ), ) - # Use wallet.coldkey as default signer if signer_keypair is not provided - if signer_keypair is None: - signer_keypair = wallet.coldkey + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type + ) + ).success: + return unlocked ml_kem_768_public_key = await subtensor.get_mev_shield_next_key() if ml_kem_768_public_key is None: @@ -158,13 +175,21 @@ async def submit_encrypted_extrinsic( error=ValueError("MEV Shield NextKey not available in storage."), ) - genesis_hash = await subtensor.get_block_hash(block=0) + inner_signing_keypair = getattr(wallet, sign_with) + + era = "00" if period is None else {"period": period} + + current_nonce = await subtensor.substrate.get_account_next_index( + account_address=inner_signing_keypair.ss58_address + ) + + signed_extrinsic = await subtensor.substrate.create_signed_extrinsic( + call=call, keypair=inner_signing_keypair, nonce=current_nonce + 1, era=era + ) - mev_commitment, mev_ciphertext, payload_core, signature = ( + mev_commitment, mev_ciphertext, payload_core = ( get_mev_commitment_and_ciphertext( - call=call, - signer_keypair=signer_keypair, - genesis_hash=genesis_hash, + signed_ext=signed_extrinsic, ml_kem_768_public_key=ml_kem_768_public_key, ) ) @@ -176,6 +201,7 @@ async def submit_encrypted_extrinsic( response = await subtensor.sign_and_send_extrinsic( wallet=wallet, + sign_with=sign_with, call=extrinsic_call, period=period, raise_error=raise_error, @@ -189,32 +215,50 @@ async def submit_encrypted_extrinsic( "ciphertext": mev_ciphertext, "ml_kem_768_public_key": ml_kem_768_public_key, "payload_core": payload_core, - "signature": signature, - "submitting_id": extrinsic_call.call_hash, + "signed_extrinsic_hash": f"0x{signed_extrinsic.extrinsic_hash.hex()}", } if wait_for_revealed_execution: triggered_events = await response.extrinsic_receipt.triggered_events + event = get_event_data_by_event_name( + events=triggered_events, + event_name="mevShield.EncryptedSubmitted", + ) - event_hash_id = get_event_attributes_by_event_name( - events=triggered_events, event_name=MEV_SUBMITTED_EVENT - )["attributes"]["id"] + if event is None: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=RuntimeError("EncryptedSubmitted event not found."), + ) - reveled_event, reveled_extrinsic = await find_revealed_extrinsic( - subtensor=subtensor, - event_names=POST_SUBMIT_MEV_EVENTS, - event_hash_id=event_hash_id, - start_block_hash=response.extrinsic_receipt.block_hash, - blocks_ahead=blocks_for_revealed_execution, - ) + shield_id = event["attributes"]["id"] - post_process_mev_response( - response=response, - revealed_name=reveled_event, - revealed_extrinsic=reveled_extrinsic, - raise_error=raise_error, + response.mev_extrinsic = await wait_for_extrinsic_by_hash( + subtensor=subtensor, + extrinsic_hash=f"0x{signed_extrinsic.extrinsic_hash.hex()}", + shield_id=shield_id, + submit_block_hash=response.extrinsic_receipt.block_hash, + timeout_blocks=blocks_for_revealed_execution, ) + if response.mev_extrinsic is None: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=RuntimeError( + "Failed to find outcome of the shield extrinsic (The protected extrinsic wasn't decrypted)." + ), + ) - logging.debug("[green]Encrypted extrinsic submitted successfully.[/green]") + if not await response.mev_extrinsic.is_success: + response.message = format_error_message( + await response.mev_extrinsic.error_message + ) + response.error = RuntimeError(response.message) + response.success = False + if raise_error: + raise response.error + else: + logging.debug( + "[green]Encrypted extrinsic submitted successfully.[/green]" + ) else: logging.error(f"[red]{response.message}[/red]") diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 25f7b2db6f..7650879807 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -379,6 +379,7 @@ async def swap_stake_extrinsic( raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index e504e511b4..64b015413f 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -66,7 +66,7 @@ async def serve_extrinsic( try: if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, unlock_type="both" + wallet, raise_error, unlock_type="hotkey" ) ).success: return unlocked @@ -112,7 +112,7 @@ async def serve_extrinsic( subtensor=subtensor, wallet=wallet, call=call, - signer_keypair=wallet.hotkey, + sign_with="hotkey", period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -214,6 +214,7 @@ async def serve_axon_extrinsic( raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) response.data = { "external_ip": external_ip, @@ -294,7 +295,7 @@ async def publish_metadata_extrinsic( subtensor=subtensor, wallet=wallet, call=call, - signer_keypair=wallet.hotkey, + sign_with="hotkey", period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 7aa2ad63e0..9225d7c503 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -111,7 +111,7 @@ async def commit_timelocked_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, - signer_keypair=wallet.hotkey, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -219,7 +219,7 @@ async def commit_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, - signer_keypair=wallet.hotkey, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -319,7 +319,7 @@ async def reveal_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, - signer_keypair=wallet.hotkey, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -416,7 +416,7 @@ async def set_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, - signer_keypair=wallet.hotkey, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/mev_shield.py b/bittensor/core/extrinsics/mev_shield.py index 7346da363c..ddcd2188b5 100644 --- a/bittensor/core/extrinsics/mev_shield.py +++ b/bittensor/core/extrinsics/mev_shield.py @@ -3,95 +3,106 @@ from typing import TYPE_CHECKING, Optional from async_substrate_interface import ExtrinsicReceipt - +from bittensor.utils import format_error_message from bittensor.core.extrinsics.pallets import MevShield from bittensor.core.extrinsics.utils import ( - get_event_attributes_by_event_name, get_mev_commitment_and_ciphertext, - post_process_mev_response, - POST_SUBMIT_MEV_EVENTS, - MEV_SUBMITTED_EVENT, + get_event_data_by_event_name, ) from bittensor.core.types import ExtrinsicResponse from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor.core.subtensor import Subtensor - from bittensor_wallet import Wallet, Keypair + from bittensor_wallet import Wallet from scalecodec.types import GenericCall -def find_revealed_extrinsic( +def wait_for_extrinsic_by_hash( subtensor: "Subtensor", - event_names: list[str], - event_hash_id: str, - start_block_hash: str, - blocks_ahead: int = 5, -) -> tuple[str, "ExtrinsicReceipt"] | tuple[None, None]: + extrinsic_hash: str, + shield_id: str, + submit_block_hash: str, + timeout_blocks: int = 3, +) -> Optional["ExtrinsicReceipt"]: """ - Searches for an extrinsic containing a specific MEV Shield event in subsequent blocks. + Wait for the result of a MeV Shield encrypted extrinsic. - This function iterates through blocks starting from the specified block hash and searches for extrinsics that - contain a MEV Shield event (DecryptedExecuted or DecryptedRejected) matching the provided wrapper_id and signer. It - checks each extrinsic's triggered events to find the matching event. + After submit_encrypted succeeds, the block author will decrypt and submit the inner extrinsic directly. This + function polls subsequent blocks looking for either: + - an extrinsic matching the provided hash (success) + OR + - a markDecryptionFailed extrinsic with matching shield ID (failure) - Parameters: - subtensor: The Subtensor instance used for blockchain queries. - event_names: The event identifiers to search for. Typically "DecryptedExecuted" or "DecryptedRejected" for MEV - Shield transactions. - event_hash_id: The wrapper_id (hash of (author, commitment, ciphertext)) to match. This uniquely identifies a - specific MEV Shield submission. - start_block_hash: The hash of the block where the search should begin. Usually the block where submit_encrypted - was included. - blocks_ahead: Maximum number of blocks to search ahead from the start block. Defaults to 5 blocks. The function - will check blocks from start_block + 1 to start_block + blocks_ahead (the start block itself is not checked, - as execute_revealed will be in subsequent blocks). + Args: + subtensor: SubtensorInterface instance. + extrinsic_hash: The hash of the inner extrinsic to find. + shield_id: The wrapper ID from EncryptedSubmitted event (for detecting decryption failures). + submit_block_hash: Block hash where submit_encrypted was included. + timeout_blocks: Max blocks to wait (default 3). Returns: - Tuple with event name and ExtrinsicReceipt object. + Optional ExtrinsicReceipt. """ - start_block_number = subtensor.substrate.get_block_number(start_block_hash) - for offset in range(1, blocks_ahead + 1): - current_block_number = start_block_number + offset + starting_block = subtensor.substrate.get_block_number(submit_block_hash) + current_block = starting_block + 1 - try: - current_block_hash = subtensor.substrate.get_block_hash( - current_block_number - ) - events = subtensor.substrate.get_events(current_block_hash) - except Exception as e: - logging.debug( - f"Error getting extrinsics for block `{current_block_number}`: {e}" - ) - continue - - for event_name in event_names: - if event := get_event_attributes_by_event_name(events, event_name): - if event["attributes"]["id"] == event_hash_id: - return event_name, ExtrinsicReceipt( - substrate=subtensor.substrate, - block_hash=current_block_hash, - extrinsic_idx=event["extrinsic_idx"], - ) + while current_block - starting_block <= timeout_blocks: + logging.debug( + f"Waiting for MEV Protection (checking block {current_block - starting_block} of {timeout_blocks})..." + ) - logging.debug(f"No {event_names} event found in block {current_block_number}.") subtensor.wait_for_block() - return None, None + block_hash = subtensor.substrate.get_block_hash(current_block) + extrinsics = subtensor.substrate.get_extrinsics(block_hash) + + result_idx = None + for idx, extrinsic in enumerate(extrinsics): + # Success: Inner extrinsic executed + if f"0x{extrinsic.extrinsic_hash.hex()}" == extrinsic_hash: + result_idx = idx + break + + # Failure: Decryption failed + call = extrinsic.value.get("call", {}) + if ( + call.get("call_module") == "MevShield" + and call.get("call_function") == "mark_decryption_failed" + ): + call_args = call.get("call_args", []) + for arg in call_args: + if arg.get("name") == "id" and arg.get("value") == shield_id: + result_idx = idx + break + if result_idx is not None: + break + + if result_idx is not None: + return ExtrinsicReceipt( + substrate=subtensor.substrate, + block_hash=block_hash, + block_number=current_block, + extrinsic_idx=result_idx, + ) + + current_block += 1 + + return None def submit_encrypted_extrinsic( subtensor: "Subtensor", wallet: "Wallet", call: "GenericCall", - signer_keypair: Optional["Keypair"] = None, + sign_with: str = "coldkey", period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, wait_for_revealed_execution: bool = True, - blocks_for_revealed_execution: int = 5, + blocks_for_revealed_execution: int = 3, ) -> ExtrinsicResponse: """ Submits an encrypted extrinsic to the MEV Shield pallet. @@ -103,17 +114,17 @@ def submit_encrypted_extrinsic( subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). call: The GenericCall object to encrypt and submit. - signer_keypair: The Keypair object used for signing the inner call. + sign_with: The keypair to use for signing the inner call/extrinsic. Can be either "coldkey" or "hotkey". period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. - wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators have + wait_for_revealed_execution: Whether to wait for the executed event, indicating that validators have successfully decrypted and executed the inner call. If True, the function will poll subsequent blocks for the event matching this submission's commitment. - blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after inclusion. + blocks_for_revealed_execution: Maximum number of blocks to poll for the executed event after inclusion. The function checks blocks from start_block + 1 to start_block + blocks_for_revealed_execution. Returns immediately if the event is found before the block limit is reached. @@ -134,10 +145,12 @@ def submit_encrypted_extrinsic( protection. """ try: - if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) - ).success: - return unlocked + if sign_with not in ["coldkey", "hotkey"]: + raise AttributeError( + f"'sign_with' must be either 'coldkey' or 'hotkey', not '{sign_with}'" + ) + + unlock_type = "both" if sign_with == "hotkey" else "coldkey" if wait_for_revealed_execution and not wait_for_inclusion: return ExtrinsicResponse.from_exception( @@ -147,9 +160,12 @@ def submit_encrypted_extrinsic( ), ) - # Use wallet.coldkey as default signer if signer_keypair is not provided - if signer_keypair is None: - signer_keypair = wallet.coldkey + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type + ) + ).success: + return unlocked ml_kem_768_public_key = subtensor.get_mev_shield_next_key() if ml_kem_768_public_key is None: @@ -158,13 +174,21 @@ def submit_encrypted_extrinsic( error=ValueError("MEV Shield NextKey not available in storage."), ) - genesis_hash = subtensor.get_block_hash(block=0) + inner_signing_keypair = getattr(wallet, sign_with) + + era = "00" if period is None else {"period": period} + + current_nonce = subtensor.substrate.get_account_next_index( + account_address=inner_signing_keypair.ss58_address + ) + + signed_extrinsic = subtensor.substrate.create_signed_extrinsic( + call=call, keypair=inner_signing_keypair, nonce=current_nonce + 1, era=era + ) - mev_commitment, mev_ciphertext, payload_core, signature = ( + mev_commitment, mev_ciphertext, payload_core = ( get_mev_commitment_and_ciphertext( - call=call, - signer_keypair=signer_keypair, - genesis_hash=genesis_hash, + signed_ext=signed_extrinsic, ml_kem_768_public_key=ml_kem_768_public_key, ) ) @@ -176,6 +200,7 @@ def submit_encrypted_extrinsic( response = subtensor.sign_and_send_extrinsic( wallet=wallet, + sign_with=sign_with, call=extrinsic_call, period=period, raise_error=raise_error, @@ -189,32 +214,51 @@ def submit_encrypted_extrinsic( "ciphertext": mev_ciphertext, "ml_kem_768_public_key": ml_kem_768_public_key, "payload_core": payload_core, - "signature": signature, - "submitting_id": extrinsic_call.call_hash, + "signed_extrinsic_hash": f"0x{signed_extrinsic.extrinsic_hash.hex()}", } + if wait_for_revealed_execution: triggered_events = response.extrinsic_receipt.triggered_events - event_hash_id = get_event_attributes_by_event_name( + event = get_event_data_by_event_name( events=triggered_events, # type: ignore - event_name=MEV_SUBMITTED_EVENT, - )["attributes"]["id"] - - reveled_event, reveled_extrinsic = find_revealed_extrinsic( - subtensor=subtensor, - event_names=POST_SUBMIT_MEV_EVENTS, - event_hash_id=event_hash_id, - start_block_hash=response.extrinsic_receipt.block_hash, - blocks_ahead=blocks_for_revealed_execution, + event_name="mevShield.EncryptedSubmitted", ) - post_process_mev_response( - response=response, - revealed_name=reveled_event, - revealed_extrinsic=reveled_extrinsic, - raise_error=raise_error, + if event is None: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=RuntimeError("EncryptedSubmitted event not found."), + ) + + shield_id = event["attributes"]["id"] + + response.mev_extrinsic = wait_for_extrinsic_by_hash( + subtensor=subtensor, + extrinsic_hash=f"0x{signed_extrinsic.extrinsic_hash.hex()}", + shield_id=shield_id, + submit_block_hash=response.extrinsic_receipt.block_hash, + timeout_blocks=blocks_for_revealed_execution, ) + if response.mev_extrinsic is None: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=RuntimeError( + "Failed to find outcome of the shield extrinsic (The protected extrinsic wasn't decrypted)." + ), + ) - logging.debug("[green]Encrypted extrinsic submitted successfully.[/green]") + if not response.mev_extrinsic.is_success: + response.message = format_error_message( + response.mev_extrinsic.error_message # type: ignore + ) + response.error = RuntimeError(response.message) + response.success = False + if raise_error: + raise response.error + else: + logging.debug( + "[green]Encrypted extrinsic submitted successfully.[/green]" + ) else: logging.error(f"[red]{response.message}[/red]") diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 7ecdafbf01..1670cb61ac 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -65,7 +65,7 @@ def serve_extrinsic( try: if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, unlock_type="both" + wallet, raise_error, unlock_type="hotkey" ) ).success: return unlocked @@ -110,6 +110,7 @@ def serve_extrinsic( subtensor=subtensor, wallet=wallet, call=call, + sign_with="hotkey", period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -209,6 +210,7 @@ def serve_axon_extrinsic( raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) response.data = { "external_ip": external_ip, @@ -288,6 +290,7 @@ def publish_metadata_extrinsic( subtensor=subtensor, wallet=wallet, call=call, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index db17f9c359..694ffa1555 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -4,8 +4,7 @@ import logging from typing import TYPE_CHECKING, Optional, Union -from bittensor_drand import encrypt_mlkem768, mlkem_kdf_id -from scalecodec import ss58_decode +from bittensor_drand import encrypt_mlkem768 from bittensor.core.extrinsics.pallets import Sudo from bittensor.core.types import ExtrinsicResponse @@ -15,19 +14,7 @@ from bittensor_wallet import Wallet from bittensor.core.chain_data import StakeInfo from bittensor.core.subtensor import Subtensor - from scalecodec.types import GenericCall - from bittensor_wallet.keypair import Keypair - from async_substrate_interface import AsyncExtrinsicReceipt, ExtrinsicReceipt - - -MEV_SUBMITTED_EVENT = "mevShield.EncryptedSubmitted" -MEV_EXECUTED_EVENT = "mevShield.DecryptedExecuted" -MEV_UNSUCCESSFUL_EVENTS = [ - "mevShield.DecryptedRejected", - "mevShield.DecryptionFailed", -] - -POST_SUBMIT_MEV_EVENTS = [MEV_EXECUTED_EVENT] + MEV_UNSUCCESSFUL_EVENTS + from scalecodec.types import GenericExtrinsic def get_old_stakes( @@ -228,11 +215,9 @@ def apply_pure_proxy_data( def get_mev_commitment_and_ciphertext( - call: "GenericCall", - signer_keypair: "Keypair", - genesis_hash: str, + signed_ext: "GenericExtrinsic", ml_kem_768_public_key: bytes, -) -> tuple[str, bytes, bytes, bytes]: +) -> tuple[str, bytes, bytes]: """ Builds MEV Shield payload and encrypts it using ML-KEM-768 + XChaCha20Poly1305. @@ -241,11 +226,7 @@ def get_mev_commitment_and_ciphertext( replay protection. Parameters: - call: The GenericCall object representing the inner call to be encrypted and executed. - signer_keypair: The Keypair used for signing the inner call payload. The signer's AccountId32 (32 bytes) is - embedded in the payload_core. - genesis_hash: The genesis block hash as a hex string (with or without "0x" prefix). Used for chain-bound - signature domain separation. + signed_ext: The signed GenericExtrinsic object representing the inner call to be encrypted and executed. ml_kem_768_public_key: The ML-KEM-768 public key bytes (1184 bytes) from NextKey storage. This key is used for encryption and its hash binds the transaction to the key epoch. @@ -254,41 +235,10 @@ def get_mev_commitment_and_ciphertext( - commitment_hex (str): Hex string of the Blake2-256 hash of payload_core (32 bytes). - ciphertext (bytes): Encrypted blob containing plaintext. - payload_core (bytes): Raw payload bytes before encryption. - - signature (bytes): MultiSignature (64 bytes for sr25519). """ - # Create payload_core: signer (32B) + key_hash (32B Blake2-256 hash) + SCALE(call) - decoded_ss58 = ss58_decode(signer_keypair.ss58_address) - decoded_ss58_cut = ( - decoded_ss58[2:] if decoded_ss58.startswith("0x") else decoded_ss58 - ) - signer_bytes = bytes.fromhex(decoded_ss58_cut) # 32 bytes + payload_core = signed_ext.data.data - # Compute key_hash = Blake2-256(NextKey_bytes) - # This binds the transaction to the key epoch at submission time - key_hash_bytes = hashlib.blake2b( - ml_kem_768_public_key, digest_size=32 - ).digest() # 32 bytes - - scale_call_bytes = bytes(call.data.data) # SCALE encoded call - mev_shield_version = mlkem_kdf_id() - - # Fix genesis_hash processing - genesis_hash_clean = ( - genesis_hash[2:] if genesis_hash.startswith("0x") else genesis_hash - ) - genesis_hash_bytes = bytes.fromhex(genesis_hash_clean) - - payload_core = signer_bytes + key_hash_bytes + scale_call_bytes - - # Sign payload: coldkey.sign(b"mev-shield:v1" + genesis_hash + payload_core) - message_to_sign = ( - b"mev-shield:" + mev_shield_version + genesis_hash_bytes + payload_core - ) - - signature = signer_keypair.sign(message_to_sign) - - # Create plaintext: payload_core + b"\x01" + signature - plaintext = payload_core + b"\x01" + signature + plaintext = bytes(payload_core) # Getting ciphertext (encrypting plaintext using ML-KEM-768) ciphertext = encrypt_mlkem768(ml_kem_768_public_key, plaintext) @@ -297,10 +247,10 @@ def get_mev_commitment_and_ciphertext( commitment_hash = hashlib.blake2b(payload_core, digest_size=32).digest() commitment_hex = "0x" + commitment_hash.hex() - return commitment_hex, ciphertext, payload_core, signature + return commitment_hex, ciphertext, payload_core -def get_event_attributes_by_event_name(events: list, event_name: str) -> Optional[dict]: +def get_event_data_by_event_name(events: list, event_name: str) -> Optional[dict]: """ Extracts event data from triggered events by event ID. @@ -328,59 +278,3 @@ def get_event_attributes_by_event_name(events: list, event_name: str) -> Optiona ): return event return None - - -def post_process_mev_response( - response: "ExtrinsicResponse", - revealed_name: Optional[str], - revealed_extrinsic: Optional["ExtrinsicReceipt | AsyncExtrinsicReceipt"], - raise_error: bool = False, -) -> None: - """ - Post-processes the result of a MEV Shield extrinsic submission by updating the response object based on the revealed - extrinsic execution status. - - This function analyzes the revealed extrinsic (execute_revealed) that was found after the initial encrypted - submission and updates the response object accordingly. It handles cases where the revealed extrinsic was not found, - where it failed (DecryptedRejected or DecryptionFailed events), and propagates errors if requested. - - Parameters: - response: The ExtrinsicResponse object from the initial submit_encrypted call. This object will be modified - in-place with the revealed extrinsic receipt and updated success/error status. - revealed_name: The name of the event found in the revealed extrinsic (e.g., "mevShield.DecryptedExecuted", - "mevShield.DecryptedRejected", "mevShield.DecryptionFailed"). - revealed_extrinsic: The ExtrinsicReceipt or AsyncExtrinsicReceipt object for the execute_revealed transaction, - if found. None if the revealed extrinsic was not found in the expected blocks. This receipt contains the - triggered events and execution details. - raise_error: If True, raises the error immediately if the response contains an error. If False, the error is - stored in response.error but not raised. Defaults to False. - - Returns: - None. The function modifies the response object in-place by setting: - - response.mev_extrinsic_receipt: The revealed extrinsic receipt - - response.success: False if revealed extrinsic not found or failed, otherwise True. - - response.message: Error message describing the failure if failure. - - response.error: RuntimeError with the response.message. - """ - # add revealed extrinsic receipt to response - response.mev_extrinsic_receipt = revealed_extrinsic - - # when main extrinsic is successful but revealed extrinsic is not found in the chain. - if revealed_extrinsic is None: - response.success = False - response.message = "MeV protected extrinsic does not contain related event." - response.error = RuntimeError(response.message) - - # when main extrinsic is successful but revealed extrinsic is not successful. - if revealed_name in MEV_UNSUCCESSFUL_EVENTS: - response.success = False - response.message = ( - f"{revealed_name}: Check `mev_extrinsic_receipt` for details." - ) - response.error = RuntimeError(response.message) - - if response.error: - if raise_error: - raise response.error - else: - response.with_log() diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 155d3cac53..6b13bc1737 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -112,6 +112,7 @@ def commit_timelocked_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -219,6 +220,7 @@ def commit_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -318,6 +320,7 @@ def reveal_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -414,6 +417,7 @@ def set_weights_extrinsic( subtensor=subtensor, wallet=wallet, call=call, + sign_with=signing_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 506038e17d..d992b07ac4 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4375,6 +4375,10 @@ def compose_call( Returns: GenericCall: Composed call object ready for extrinsic submission. + + Notes: + For detailed documentation and examples of composing calls, including the CallBuilder utility, see: + """ call_params = self.validate_extrinsic_params( call_module, call_function, call_params, block @@ -5365,14 +5369,14 @@ def mev_submit_encrypted( self, wallet: "Wallet", call: "GenericCall", - signer_keypair: Optional["Keypair"] = None, + sign_with: str = "coldkey", *, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, wait_for_revealed_execution: bool = True, - blocks_for_revealed_execution: int = 5, + blocks_for_revealed_execution: int = 3, ) -> ExtrinsicResponse: """ Submits an encrypted extrinsic to the MEV Shield pallet. @@ -5383,19 +5387,19 @@ def mev_submit_encrypted( Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). call: The GenericCall object to encrypt and submit. - signer_keypair: The keypair used to sign the inner call. + sign_with: The keypair to use for signing the inner call/extrinsic. Can be either "coldkey" or "hotkey". period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. - wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators + wait_for_revealed_execution: Whether to wait for the executed event, indicating that validators have successfully decrypted and executed the inner call. If True, the function will poll subsequent blocks for the event matching this submission's commitment. - blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after - inclusion. The function checks blocks from start_block+1 to start_block + blocks_for_revealed_execution. - Returns immediately if the event is found before the block limit is reached. + blocks_for_revealed_execution: Maximum number of blocks to poll for the executed event after inclusion. The + function checks blocks from start_block+1 to start_block + blocks_for_revealed_execution. Returns + immediately if the event is found before the block limit is reached. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5409,12 +5413,19 @@ def mev_submit_encrypted( payload_core = signer_bytes (32B) + nonce (u32 LE, 4B) + SCALE(call) plaintext = payload_core + b"\\x01" + signature (64B for sr25519) commitment = blake2_256(payload_core) + + Notes: + For detailed documentation and examples of MEV Shield protection, see: + + + For creating GenericCall objects to use with this method, see: + """ return submit_encrypted_extrinsic( subtensor=self, wallet=wallet, call=call, - signer_keypair=signer_keypair, + sign_with=sign_with, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -6682,7 +6693,6 @@ def set_weights( """ attempt = 0 response = ExtrinsicResponse(False) - if attempt_check := validate_max_attempts(max_attempts, response): return attempt_check diff --git a/bittensor/core/types.py b/bittensor/core/types.py index fa55c53d9e..a30cfe3b17 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -301,11 +301,11 @@ class ExtrinsicResponse: contains the most detailed execution data available, including the block number and hash, triggered events, extrinsic index, execution phase, and other low-level details. This allows deep debugging or post-analysis of on-chain execution. - mev_extrinsic_receipt: The receipt object of the revealed (decrypted and executed) MEV Shield extrinsic. This is + mev_extrinsic: The extrinsic object of the revealed (decrypted and executed) MEV Shield extrinsic. This is populated when using MEV Shield protection (``with_mev_protection=True``) and contains the execution details - of the second extrinsic that decrypts and executes the originally encrypted call. Contains triggered events - such as ``DecryptedExecuted`` or ``DecryptedRejected``, block information, and other execution metadata. Set - to ``None`` for non-MEV Shield transactions or when the revealed extrinsic receipt is not available. + of the second extrinsic that decrypts and executes the originally encrypted call. Contains triggered events, + block information, and other execution metadata. Set to ``None`` for non-MEV Shield transactions or when the + revealed extrinsic receipt is not available. transaction_tao_fee: TAO fee charged by the transaction in TAO (e.g., fee for add_stake), if available. transaction_alpha_fee: Alpha fee charged by the transaction (e.g., fee for transfer_stake), if available. error: Captures the underlying exception if the extrinsic failed, otherwise `None`. @@ -336,7 +336,7 @@ class ExtrinsicResponse: extrinsic: {'account_id': '0xd43593c715fdd31c... transaction_fee: τ1.0 extrinsic_receipt: Extrinsic Receipt data of of the submitted extrinsic - mev_extrinsic_receipt: None + mev_extrinsic: None transaction_tao_fee: τ1.0 transaction_alpha_fee: 1.0β error: None @@ -359,7 +359,7 @@ class ExtrinsicResponse: extrinsic: Optional["GenericExtrinsic"] = None extrinsic_fee: Optional["Balance"] = None extrinsic_receipt: Optional["AsyncExtrinsicReceipt | ExtrinsicReceipt"] = None - mev_extrinsic_receipt: Optional["AsyncExtrinsicReceipt | ExtrinsicReceipt"] = None + mev_extrinsic: Optional["AsyncExtrinsicReceipt | ExtrinsicReceipt"] = None transaction_tao_fee: Optional["Balance"] = None transaction_alpha_fee: Optional["Balance"] = None error: Optional[Exception] = None @@ -383,7 +383,7 @@ def __str__(self): f"\textrinsic: {self.extrinsic}\n" f"\textrinsic_fee: {self.extrinsic_fee}\n" f"\textrinsic_receipt: {_extrinsic_receipt}\n" - f"\tmev_extrinsic_receipt: {self.mev_extrinsic_receipt}\n" + f"\tmev_extrinsic: {self.mev_extrinsic}\n" f"\ttransaction_tao_fee: {self.transaction_tao_fee}\n" f"\ttransaction_alpha_fee: {self.transaction_alpha_fee}\n" f"\terror: {self.error}\n" diff --git a/bittensor/extras/dev_framework/subnet.py b/bittensor/extras/dev_framework/subnet.py index 7cdfbef7ae..f5dd5e9561 100644 --- a/bittensor/extras/dev_framework/subnet.py +++ b/bittensor/extras/dev_framework/subnet.py @@ -1,7 +1,5 @@ from typing import Optional, Union from collections import namedtuple -from async_substrate_interface.async_substrate import AsyncExtrinsicReceipt -from async_substrate_interface.sync_substrate import ExtrinsicReceipt from bittensor_wallet import Wallet from bittensor.core.extrinsics.asyncex.utils import ( @@ -217,12 +215,7 @@ def _register_subnet( wait_for_finalization=wait_for_finalization or self.wait_for_finalization, ) self._check_response(response) - if netuid := _set_netuid_from_register_response( - response.extrinsic_receipt.triggered_events - ): - self._netuid = netuid - else: - self._netuid = self.s.subnets.get_total_subnets() - 1 + self._netuid = self.s.subnets.get_total_subnets() - 1 if response.success: self._owner = owner_wallet logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") @@ -247,12 +240,7 @@ async def _async_register_subnet( wait_for_finalization=wait_for_finalization or self.wait_for_finalization, ) self._check_response(response) - if netuid := _set_netuid_from_register_response( - await response.extrinsic_receipt.triggered_events - ): - self._netuid = netuid - else: - self._netuid = self.s.subnets.get_total_subnets() - 1 + self._netuid = await self.s.subnets.get_total_subnets() - 1 if response.success: self._owner = owner_wallet logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") @@ -310,7 +298,7 @@ async def _async_activate_subnet( ) ).value # added 10 blocks bc 2.5 seconds is not always enough for the chain to update. - await self.s.wait_for_block(current_block + activation_block + 10) + await self.s.wait_for_block(current_block + activation_block + 1) response = await self.s.subnets.start_call( wallet=owner_wallet, @@ -503,13 +491,3 @@ def _check_register_subnet(self): "This instance already has associated netuid. Cannot register again. " "To register a new subnet, create a new instance of TestSubnet class." ) - - -def _set_netuid_from_register_response( - events: Union["AsyncExtrinsicReceipt", "ExtrinsicReceipt"], -): - """Get netuid from the register subnet response.""" - for event in events: - if event.get("event_id") == "NetworkAdded": - return event.get("attributes")[0] - return None diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index fa26b0b745..df846442c9 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -46,6 +46,10 @@ MetagraphInfoPool, NeuronInfo, NeuronInfoLite, + ProxyAnnouncementInfo, + ProxyConstants, + ProxyInfo, + ProxyType, PrometheusInfo, ProposalCallData, ProposalVoteData, @@ -198,6 +202,10 @@ "PostProcessException", "PriorityException", "ProportionOverflow", + "ProxyAnnouncementInfo", + "ProxyConstants", + "ProxyInfo", + "ProxyType", "RegistrationError", "RegistrationNotPermittedOnRootSubnet", "RunException", diff --git a/pyproject.toml b/pyproject.toml index 3f95468c9e..19a429c14f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=1.2.0,<2.0.0", "bittensor-wallet>=4.0.0,<5.0", - "async-substrate-interface>=1.5.13" + "async-substrate-interface>=1.5.14" ] [project.optional-dependencies] diff --git a/tests/e2e_tests/test_mev_shield.py b/tests/e2e_tests/test_mev_shield.py index d54934599d..05889bbf61 100644 --- a/tests/e2e_tests/test_mev_shield.py +++ b/tests/e2e_tests/test_mev_shield.py @@ -20,7 +20,7 @@ @pytest.mark.parametrize("local_chain", [False], indirect=True) def test_mev_shield_happy_path( - subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet, local_chain + subtensor, alice_wallet, bob_wallet, charlie_wallet, local_chain ): """Tests MEV Shield functionality with add_stake inner call. @@ -59,54 +59,44 @@ def test_mev_shield_happy_path( next_epoch_start_block + subtensor.subnets.tempo(bob_sn.netuid) * 2 ) - for signer in [None, dave_wallet.coldkey]: - stake_before = subtensor.staking.get_stake( - coldkey_ss58=signer.ss58_address - if signer is not None - else alice_wallet.coldkey.ss58_address, - hotkey_ss58=charlie_wallet.hotkey.ss58_address, - netuid=bob_sn.netuid, - ) - logging.console.info(f"Stake before: {stake_before}") + stake_before = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake before: {stake_before}") - subnet_price = subtensor.subnets.get_subnet_price(2) - limit_price = (subnet_price * 2).rao + subnet_price = subtensor.subnets.get_subnet_price(2) + limit_price = (subnet_price * 2).rao - call = pallets.SubtensorModule(subtensor).add_stake_limit( - netuid=bob_sn.netuid, - hotkey=charlie_wallet.hotkey.ss58_address, - amount_staked=Balance.from_tao(5).rao, - allow_partial=True, - limit_price=limit_price, - ) - - response = subtensor.mev_shield.mev_submit_encrypted( - wallet=alice_wallet, - call=call, - signer_keypair=signer, - raise_error=True, - ) + call = pallets.SubtensorModule(subtensor).add_stake_limit( + netuid=bob_sn.netuid, + hotkey=charlie_wallet.hotkey.ss58_address, + amount_staked=Balance.from_tao(5).rao, + allow_partial=True, + limit_price=limit_price, + ) - assert response.success, response.message - assert response.mev_extrinsic_receipt is not None, ( - "No revealed extrinsic receipt." - ) + response = subtensor.mev_shield.mev_submit_encrypted( + wallet=alice_wallet, + call=call, + raise_error=True, + ) + assert response.success, response.message - stake_after = subtensor.staking.get_stake( - coldkey_ss58=signer.ss58_address - if signer is not None - else alice_wallet.coldkey.ss58_address, - hotkey_ss58=charlie_wallet.hotkey.ss58_address, - netuid=bob_sn.netuid, - ) - logging.console.info(f"Stake after: {stake_after}") - assert stake_after > stake_before + stake_after = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake after: {stake_after}") + assert stake_after > stake_before @pytest.mark.parametrize("local_chain", [False], indirect=True) @pytest.mark.asyncio async def test_mev_shield_happy_path_async( - async_subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet, local_chain + async_subtensor, alice_wallet, bob_wallet, charlie_wallet, local_chain ): """Async tests MEV Shield functionality with add_stake inner call. @@ -147,45 +137,35 @@ async def test_mev_shield_happy_path_async( + await async_subtensor.subnets.tempo(bob_sn.netuid) * 2 ) - for signer in [None, dave_wallet.coldkey]: - stake_before = await async_subtensor.staking.get_stake( - coldkey_ss58=signer.ss58_address - if signer is not None - else alice_wallet.coldkey.ss58_address, - hotkey_ss58=charlie_wallet.hotkey.ss58_address, - netuid=bob_sn.netuid, - ) - logging.console.info(f"Stake before: {stake_before}") + stake_before = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake before: {stake_before}") - subnet_price = await async_subtensor.subnets.get_subnet_price(2) - limit_price = (subnet_price * 2).rao + subnet_price = await async_subtensor.subnets.get_subnet_price(2) + limit_price = (subnet_price * 2).rao - call = await pallets.SubtensorModule(async_subtensor).add_stake_limit( - netuid=bob_sn.netuid, - hotkey=charlie_wallet.hotkey.ss58_address, - amount_staked=Balance.from_tao(5).rao, - allow_partial=True, - limit_price=limit_price, - ) - - response = await async_subtensor.mev_shield.mev_submit_encrypted( - wallet=alice_wallet, - call=call, - signer_keypair=signer, - raise_error=True, - ) + call = await pallets.SubtensorModule(async_subtensor).add_stake_limit( + netuid=bob_sn.netuid, + hotkey=charlie_wallet.hotkey.ss58_address, + amount_staked=Balance.from_tao(5).rao, + allow_partial=True, + limit_price=limit_price, + ) - assert response.success, response.message - assert response.mev_extrinsic_receipt is not None, ( - "No revealed extrinsic receipt." - ) + response = await async_subtensor.mev_shield.mev_submit_encrypted( + wallet=alice_wallet, + call=call, + raise_error=True, + ) + assert response.success, response.message - stake_after = await async_subtensor.staking.get_stake( - coldkey_ss58=signer.ss58_address - if signer is not None - else alice_wallet.coldkey.ss58_address, - hotkey_ss58=charlie_wallet.hotkey.ss58_address, - netuid=bob_sn.netuid, - ) - logging.console.info(f"Stake after: {stake_after}") - assert stake_after > stake_before + stake_after = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake after: {stake_after}") + assert stake_after > stake_before diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 714cfc5c18..aee040b3f1 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -6247,7 +6247,6 @@ async def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): """Test mev_submit_encrypted calls submit_encrypted_extrinsic correctly.""" # Prep fake_call = mocker.Mock(spec=GenericCall) - fake_signer_keypair = mocker.Mock() fake_period = 128 fake_raise_error = False fake_wait_for_inclusion = True @@ -6264,7 +6263,6 @@ async def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): result = await subtensor.mev_submit_encrypted( wallet=fake_wallet, call=fake_call, - signer_keypair=fake_signer_keypair, period=fake_period, raise_error=fake_raise_error, wait_for_inclusion=fake_wait_for_inclusion, @@ -6278,7 +6276,7 @@ async def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, call=fake_call, - signer_keypair=fake_signer_keypair, + sign_with="coldkey", period=fake_period, raise_error=fake_raise_error, wait_for_inclusion=fake_wait_for_inclusion, @@ -6308,12 +6306,12 @@ async def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocke subtensor=subtensor, wallet=fake_wallet, call=fake_call, - signer_keypair=None, + sign_with="coldkey", period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, wait_for_revealed_execution=True, - blocks_for_revealed_execution=5, + blocks_for_revealed_execution=3, ) assert result == mocked_submit_encrypted_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 683d94bf1d..0d70415874 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -6365,7 +6365,6 @@ def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): """Test mev_submit_encrypted calls submit_encrypted_extrinsic correctly.""" # Prep fake_call = mocker.Mock(spec=GenericCall) - fake_signer_keypair = mocker.Mock() fake_period = 128 fake_raise_error = False fake_wait_for_inclusion = True @@ -6381,7 +6380,6 @@ def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): result = subtensor.mev_submit_encrypted( wallet=fake_wallet, call=fake_call, - signer_keypair=fake_signer_keypair, period=fake_period, raise_error=fake_raise_error, wait_for_inclusion=fake_wait_for_inclusion, @@ -6395,7 +6393,7 @@ def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, call=fake_call, - signer_keypair=fake_signer_keypair, + sign_with="coldkey", period=fake_period, raise_error=fake_raise_error, wait_for_inclusion=fake_wait_for_inclusion, @@ -6423,12 +6421,12 @@ def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, call=fake_call, - signer_keypair=None, + sign_with="coldkey", period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, wait_for_revealed_execution=True, - blocks_for_revealed_execution=5, + blocks_for_revealed_execution=3, ) assert result == mocked_submit_encrypted_extrinsic.return_value