From 518b5b3f13d67f59fd85c7275d3d23336251d12a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 04:56:33 -0800 Subject: [PATCH 01/20] update `ExtrinsicResponse` --- bittensor/core/types.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index fa55c53d9e..6405278654 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -301,7 +301,7 @@ 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 @@ -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" From 77f6a39c25e5788b64c0e66b48052f19423bb972 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 04:57:20 -0800 Subject: [PATCH 02/20] update `utils` --- bittensor/core/extrinsics/utils.py | 124 +++-------------------------- 1 file changed, 9 insertions(+), 115 deletions(-) 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() From b98c2951f675b27cb85ab19f0ced093a1e87a7f1 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 04:57:41 -0800 Subject: [PATCH 03/20] bumping ASI version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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] From e661b3ddc8bc78e729737d4428201cb3a92e0258 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 04:58:00 -0800 Subject: [PATCH 04/20] update SubtensorApi --- bittensor/extras/dev_framework/subnet.py | 28 +++--------------------- 1 file changed, 3 insertions(+), 25 deletions(-) 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 From d959b8c73b6d9b625a5f1f75219f5355797d8758 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 05:05:34 -0800 Subject: [PATCH 05/20] update extrinsics --- .../core/extrinsics/asyncex/mev_shield.py | 210 ++++++++++------- bittensor/core/extrinsics/mev_shield.py | 212 +++++++++++------- 2 files changed, 253 insertions(+), 169 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/mev_shield.py b/bittensor/core/extrinsics/asyncex/mev_shield.py index 40feffeb23..d34ec1dad8 100644 --- a/bittensor/core/extrinsics/asyncex/mev_shield.py +++ b/bittensor/core/extrinsics/asyncex/mev_shield.py @@ -6,92 +6,103 @@ 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, + 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,7 +114,7 @@ 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. @@ -134,10 +145,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 +160,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 +174,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, ) ) @@ -189,32 +213,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/mev_shield.py b/bittensor/core/extrinsics/mev_shield.py index 7346da363c..da353ac911 100644 --- a/bittensor/core/extrinsics/mev_shield.py +++ b/bittensor/core/extrinsics/mev_shield.py @@ -3,95 +3,105 @@ 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, + 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,7 +113,7 @@ 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. @@ -134,10 +144,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 +159,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 +173,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, ) ) @@ -189,32 +212,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]") From 67505f5a0f0067fac04299751d9c1dedfda0558b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 05:05:41 -0800 Subject: [PATCH 06/20] update methods --- bittensor/core/async_subtensor.py | 3 --- bittensor/core/subtensor.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index cfb02af0aa..d4b5aa69de 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -6645,7 +6645,6 @@ async def mev_submit_encrypted( self, wallet: "Wallet", call: "GenericCall", - signer_keypair: Optional["Keypair"] = None, *, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, @@ -6663,7 +6662,6 @@ 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. 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. @@ -6694,7 +6692,6 @@ async def mev_submit_encrypted( subtensor=self, wallet=wallet, call=call, - signer_keypair=signer_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..8df0abdda9 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -5365,7 +5365,6 @@ def mev_submit_encrypted( self, wallet: "Wallet", call: "GenericCall", - signer_keypair: Optional["Keypair"] = None, *, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, @@ -5383,7 +5382,6 @@ 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. 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. @@ -5414,7 +5412,6 @@ def mev_submit_encrypted( subtensor=self, wallet=wallet, call=call, - signer_keypair=signer_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, From ecced36b88bf7b6f4c32ca879080de9d854a5081 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 05:05:50 -0800 Subject: [PATCH 07/20] fix e2e tests --- tests/e2e_tests/test_mev_shield.py | 140 +++++++++++++---------------- 1 file changed, 60 insertions(+), 80 deletions(-) diff --git a/tests/e2e_tests/test_mev_shield.py b/tests/e2e_tests/test_mev_shield.py index d54934599d..d23f57993a 100644 --- a/tests/e2e_tests/test_mev_shield.py +++ b/tests/e2e_tests/test_mev_shield.py @@ -18,9 +18,9 @@ TEMPO_TO_SET = 3 -@pytest.mark.parametrize("local_chain", [False], indirect=True) +# @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.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 From 7e0bb823cb4aab979a0622e8a34b512339c7ac79 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 05:17:14 -0800 Subject: [PATCH 08/20] fix unit tests + ruff --- bittensor/core/async_subtensor.py | 4 ++-- bittensor/core/extrinsics/asyncex/mev_shield.py | 4 ++-- bittensor/core/extrinsics/mev_shield.py | 4 ++-- bittensor/core/subtensor.py | 8 ++++---- bittensor/core/types.py | 6 +++--- tests/unit_tests/test_async_subtensor.py | 3 --- tests/unit_tests/test_subtensor.py | 3 --- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d4b5aa69de..bc3ca3b6af 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -6668,10 +6668,10 @@ async def mev_submit_encrypted( 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. diff --git a/bittensor/core/extrinsics/asyncex/mev_shield.py b/bittensor/core/extrinsics/asyncex/mev_shield.py index d34ec1dad8..fbb79822b9 100644 --- a/bittensor/core/extrinsics/asyncex/mev_shield.py +++ b/bittensor/core/extrinsics/asyncex/mev_shield.py @@ -121,10 +121,10 @@ async def submit_encrypted_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 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. diff --git a/bittensor/core/extrinsics/mev_shield.py b/bittensor/core/extrinsics/mev_shield.py index da353ac911..48e369b911 100644 --- a/bittensor/core/extrinsics/mev_shield.py +++ b/bittensor/core/extrinsics/mev_shield.py @@ -120,10 +120,10 @@ def submit_encrypted_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 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. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 8df0abdda9..56eb5be633 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -5388,12 +5388,12 @@ def mev_submit_encrypted( 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. diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 6405278654..a30cfe3b17 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -303,9 +303,9 @@ class ExtrinsicResponse: of on-chain execution. 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`. diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 714cfc5c18..ee7c10f811 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -6264,7 +6264,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 +6277,6 @@ async def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): subtensor=subtensor, 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, @@ -6308,7 +6306,6 @@ async def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocke subtensor=subtensor, wallet=fake_wallet, call=fake_call, - signer_keypair=None, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 683d94bf1d..82b9687d2e 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -6381,7 +6381,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 +6394,6 @@ def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): subtensor=subtensor, 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, @@ -6423,7 +6421,6 @@ def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, call=fake_call, - signer_keypair=None, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, From 5f003de837b23340974cdc7b3d9f8583d7b71379 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 05:56:58 -0800 Subject: [PATCH 09/20] update `sign_with` for `weights` and `serve` extrinsics --- bittensor/core/extrinsics/asyncex/serving.py | 4 ++-- bittensor/core/extrinsics/asyncex/weights.py | 8 ++++---- bittensor/core/extrinsics/serving.py | 2 ++ bittensor/core/extrinsics/weights.py | 4 ++++ bittensor/core/subtensor.py | 1 - 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index e504e511b4..a4d6909d69 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -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, @@ -294,7 +294,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/serving.py b/bittensor/core/extrinsics/serving.py index 7ecdafbf01..584420c534 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -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, @@ -288,6 +289,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/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 56eb5be633..e386c07072 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -6679,7 +6679,6 @@ def set_weights( """ attempt = 0 response = ExtrinsicResponse(False) - if attempt_check := validate_max_attempts(max_attempts, response): return attempt_check From e3c89f6e3a6ad06659706bf08d6a992bed7681d7 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 06:12:27 -0800 Subject: [PATCH 10/20] add `block_number` to `AsyncExtrinsicReceipt` --- bittensor/core/extrinsics/asyncex/mev_shield.py | 1 + bittensor/core/extrinsics/mev_shield.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bittensor/core/extrinsics/asyncex/mev_shield.py b/bittensor/core/extrinsics/asyncex/mev_shield.py index fbb79822b9..c9e54e6a2b 100644 --- a/bittensor/core/extrinsics/asyncex/mev_shield.py +++ b/bittensor/core/extrinsics/asyncex/mev_shield.py @@ -84,6 +84,7 @@ async def wait_for_extrinsic_by_hash( return AsyncExtrinsicReceipt( substrate=subtensor.substrate, block_hash=block_hash, + block_number=current_block, extrinsic_idx=result_idx, ) diff --git a/bittensor/core/extrinsics/mev_shield.py b/bittensor/core/extrinsics/mev_shield.py index 48e369b911..cbf72fc943 100644 --- a/bittensor/core/extrinsics/mev_shield.py +++ b/bittensor/core/extrinsics/mev_shield.py @@ -83,6 +83,7 @@ def wait_for_extrinsic_by_hash( return ExtrinsicReceipt( substrate=subtensor.substrate, block_hash=block_hash, + block_number=current_block, extrinsic_idx=result_idx, ) From 7c29511af81d2689fbffabbdcbcdb0640da33a69 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 12:33:28 -0800 Subject: [PATCH 11/20] add missed param to top level/subtensor method call --- bittensor/core/async_subtensor.py | 3 +++ bittensor/core/subtensor.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index bc3ca3b6af..2e9a692687 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -6645,6 +6645,7 @@ async def mev_submit_encrypted( self, wallet: "Wallet", call: "GenericCall", + sign_with: str = "coldkey", *, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, @@ -6662,6 +6663,7 @@ 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. + 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. @@ -6692,6 +6694,7 @@ async def mev_submit_encrypted( subtensor=self, wallet=wallet, call=call, + sign_with=sign_with, 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 e386c07072..87effe46fa 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -5365,6 +5365,7 @@ def mev_submit_encrypted( self, wallet: "Wallet", call: "GenericCall", + sign_with: str = "coldkey", *, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, @@ -5382,6 +5383,7 @@ 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. + 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. @@ -5412,6 +5414,7 @@ def mev_submit_encrypted( subtensor=self, wallet=wallet, call=call, + sign_with=sign_with, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, From 667692a07dcdaa42ac45352e683cdb413f530389 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 12:33:41 -0800 Subject: [PATCH 12/20] make imports easy --- bittensor/utils/easy_imports.py | 8 ++++++++ 1 file changed, 8 insertions(+) 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", From 9c9d5187a44c75575ccf2357b44a9343eff61bef Mon Sep 17 00:00:00 2001 From: michael trestman Date: Fri, 5 Dec 2025 12:47:54 -0800 Subject: [PATCH 13/20] wip --- bittensor/core/async_subtensor.py | 11 +++++++++++ bittensor/core/subtensor.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index bc3ca3b6af..b88a3c41b1 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) @@ -6687,6 +6691,13 @@ 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, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e386c07072..24d619e911 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 @@ -5407,6 +5411,13 @@ 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, From c95af9dc30839e129c5a5591f237f787e59b8861 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 13:57:29 -0800 Subject: [PATCH 14/20] fix unit tests --- tests/unit_tests/test_async_subtensor.py | 3 ++- tests/unit_tests/test_subtensor.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index ee7c10f811..9e8d881773 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 @@ -6277,6 +6276,7 @@ async def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, call=fake_call, + sign_with="coldkey", period=fake_period, raise_error=fake_raise_error, wait_for_inclusion=fake_wait_for_inclusion, @@ -6306,6 +6306,7 @@ async def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocke subtensor=subtensor, wallet=fake_wallet, call=fake_call, + sign_with="coldkey", period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 82b9687d2e..5a6e0a4c35 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 @@ -6394,6 +6393,7 @@ def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, call=fake_call, + sign_with="coldkey", period=fake_period, raise_error=fake_raise_error, wait_for_inclusion=fake_wait_for_inclusion, @@ -6421,6 +6421,7 @@ def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, call=fake_call, + sign_with="coldkey", period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, From f6ae165b7449bbc52cc614060d990f3b38f06e51 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 13:57:49 -0800 Subject: [PATCH 15/20] e2e test -> non-fast=runtime --- tests/e2e_tests/test_mev_shield.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_mev_shield.py b/tests/e2e_tests/test_mev_shield.py index d23f57993a..05889bbf61 100644 --- a/tests/e2e_tests/test_mev_shield.py +++ b/tests/e2e_tests/test_mev_shield.py @@ -18,7 +18,7 @@ TEMPO_TO_SET = 3 -# @pytest.mark.parametrize("local_chain", [False], indirect=True) +@pytest.mark.parametrize("local_chain", [False], indirect=True) def test_mev_shield_happy_path( subtensor, alice_wallet, bob_wallet, charlie_wallet, local_chain ): @@ -93,7 +93,7 @@ def test_mev_shield_happy_path( assert stake_after > stake_before -# @pytest.mark.parametrize("local_chain", [False], indirect=True) +@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, local_chain From 5b508749139f2eacfe3ac0d294c87c23a5c8e7bb Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 17:33:49 -0800 Subject: [PATCH 16/20] default for blocks_for_revealed_execution = 3 --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/subtensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 2658072c57..d8e5111e3f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -6656,7 +6656,7 @@ async def mev_submit_encrypted( 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. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0cd472408d..d992b07ac4 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -5376,7 +5376,7 @@ def mev_submit_encrypted( 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. From 6820b40934a50c6143ee56109ede772196afcff6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 17:34:05 -0800 Subject: [PATCH 17/20] hk --- bittensor/core/extrinsics/asyncex/mev_shield.py | 1 + bittensor/core/extrinsics/mev_shield.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/mev_shield.py b/bittensor/core/extrinsics/asyncex/mev_shield.py index c9e54e6a2b..b60f4ddad9 100644 --- a/bittensor/core/extrinsics/asyncex/mev_shield.py +++ b/bittensor/core/extrinsics/asyncex/mev_shield.py @@ -201,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, diff --git a/bittensor/core/extrinsics/mev_shield.py b/bittensor/core/extrinsics/mev_shield.py index cbf72fc943..ddcd2188b5 100644 --- a/bittensor/core/extrinsics/mev_shield.py +++ b/bittensor/core/extrinsics/mev_shield.py @@ -200,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, @@ -248,8 +249,8 @@ def submit_encrypted_extrinsic( if not response.mev_extrinsic.is_success: response.message = format_error_message( - response.mev_extrinsic.error_message - ) # type: ignore + response.mev_extrinsic.error_message # type: ignore + ) response.error = RuntimeError(response.message) response.success = False if raise_error: From 3ac5b8dc60606219aae4766481722a0b796e75a9 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 17:34:42 -0800 Subject: [PATCH 18/20] fix the missed --- bittensor/core/extrinsics/asyncex/children.py | 2 ++ bittensor/core/extrinsics/asyncex/move_stake.py | 1 + bittensor/core/extrinsics/asyncex/serving.py | 1 + bittensor/core/extrinsics/serving.py | 1 + 4 files changed, 5 insertions(+) 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/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 a4d6909d69..4e628a70fa 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -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, diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 584420c534..93a5832eee 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -210,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, From 004a86721bcdf341bfb7203934ecc9e88c458e30 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 17:35:53 -0800 Subject: [PATCH 19/20] unit tests --- tests/unit_tests/test_async_subtensor.py | 2 +- tests/unit_tests/test_subtensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 9e8d881773..aee040b3f1 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -6312,6 +6312,6 @@ async def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocke 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 5a6e0a4c35..0d70415874 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -6427,6 +6427,6 @@ def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocker): 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 From c961aabff89cd2163bb7b6ea2d629710926687fd Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 5 Dec 2025 17:39:47 -0800 Subject: [PATCH 20/20] HK more --- bittensor/core/extrinsics/asyncex/serving.py | 2 +- bittensor/core/extrinsics/serving.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 4e628a70fa..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 diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 93a5832eee..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