Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 4 additions & 42 deletions bittensor_cli/src/bittensor/extrinsics/mev_shield.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import hashlib
from typing import TYPE_CHECKING, Optional

from async_substrate_interface import AsyncExtrinsicReceipt
Expand Down Expand Up @@ -37,48 +36,25 @@ async def encrypt_extrinsic(
plaintext = bytes(signed_extrinsic.data.data)

# Encrypt using ML-KEM-768
ciphertext = encrypt_mlkem768(ml_kem_768_public_key, plaintext)

# Commitment: blake2_256(payload_core)
commitment_hash = hashlib.blake2b(plaintext, digest_size=32).digest()
commitment_hex = "0x" + commitment_hash.hex()
ciphertext = encrypt_mlkem768(
ml_kem_768_public_key, plaintext, include_key_hash=True
)

# Create the MevShield.submit_encrypted call
encrypted_call = await subtensor.substrate.compose_call(
call_module="MevShield",
call_function="submit_encrypted",
call_params={
"commitment": commitment_hex,
"ciphertext": ciphertext,
},
)

return encrypted_call


async def extract_mev_shield_id(response: "AsyncExtrinsicReceipt") -> Optional[str]:
"""
Extract the MEV Shield wrapper ID from an extrinsic response.

After submitting a MEV Shield encrypted call, the EncryptedSubmitted event
contains the wrapper ID needed to track execution.

Args:
response: The extrinsic receipt from submit_extrinsic.

Returns:
The wrapper ID (hex string) or None if not found.
"""
for event in await response.triggered_events:
if event["event_id"] == "EncryptedSubmitted":
return event["attributes"]["id"]
return None


async def wait_for_extrinsic_by_hash(
subtensor: "SubtensorInterface",
extrinsic_hash: str,
shield_id: str,
submit_block_hash: str,
timeout_blocks: int = 2,
status=None,
Expand Down Expand Up @@ -112,7 +88,7 @@ async def _noop(_):
return True

starting_block = await subtensor.substrate.get_block_number(submit_block_hash)
current_block = starting_block + 1
current_block = starting_block

while current_block - starting_block <= timeout_blocks:
if status:
Expand All @@ -137,20 +113,6 @@ async def _noop(_):
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:
receipt = AsyncExtrinsicReceipt(
substrate=subtensor.substrate,
Expand Down
5 changes: 5 additions & 0 deletions bittensor_cli/src/bittensor/subtensor_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,11 @@ async def create_signed(call_to_sign, n):
return False, format_error_message(await response.error_message), None
except SubstrateRequestException as e:
err_msg = format_error_message(e)
if mev_protection and "'result': 'invalid'" in str(e).lower():
err_msg = (
f"MEV Shield extrinsic rejected as invalid. "
f"This usually means the MEV Shield NextKey changed between fetching and submission."
)
if proxy and "Invalid Transaction" in err_msg:
extrinsic_fee, signer_balance = await asyncio.gather(
self.get_extrinsic_fee(
Expand Down
5 changes: 0 additions & 5 deletions bittensor_cli/src/commands/stake/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from bittensor_cli.src import COLOR_PALETTE
from bittensor_cli.src.bittensor.balances import Balance
from bittensor_cli.src.bittensor.extrinsics.mev_shield import (
extract_mev_shield_id,
wait_for_extrinsic_by_hash,
)
from bittensor_cli.src.bittensor.utils import (
Expand Down Expand Up @@ -167,11 +166,9 @@ async def safe_stake_extrinsic(
else:
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status_,
)
Expand Down Expand Up @@ -259,11 +256,9 @@ async def stake_extrinsic(
else:
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status_,
)
Expand Down
7 changes: 0 additions & 7 deletions bittensor_cli/src/commands/stake/move.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from bittensor_cli.src import COLOR_PALETTE
from bittensor_cli.src.bittensor.balances import Balance
from bittensor_cli.src.bittensor.extrinsics.mev_shield import (
extract_mev_shield_id,
wait_for_extrinsic_by_hash,
)
from bittensor_cli.src.bittensor.utils import (
Expand Down Expand Up @@ -691,11 +690,9 @@ async def move_stake(
if success_:
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status,
)
Expand Down Expand Up @@ -909,11 +906,9 @@ async def transfer_stake(
if success_:
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status,
)
Expand Down Expand Up @@ -1147,11 +1142,9 @@ async def swap_stake(
if success_:
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status,
)
Expand Down
7 changes: 0 additions & 7 deletions bittensor_cli/src/commands/stake/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

from bittensor_cli.src import COLOR_PALETTE
from bittensor_cli.src.bittensor.extrinsics.mev_shield import (
extract_mev_shield_id,
wait_for_extrinsic_by_hash,
)
from bittensor_cli.src.bittensor.balances import Balance
Expand Down Expand Up @@ -652,11 +651,9 @@ async def _unstake_extrinsic(
if success:
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status,
)
Expand Down Expand Up @@ -767,11 +764,9 @@ async def _safe_unstake_extrinsic(
if success:
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status,
)
Expand Down Expand Up @@ -897,11 +892,9 @@ async def _unstake_all_extrinsic(

if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status,
)
Expand Down
3 changes: 0 additions & 3 deletions bittensor_cli/src/commands/subnets/subnets.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
)
from bittensor_cli.src.bittensor.extrinsics.root import root_register_extrinsic
from bittensor_cli.src.bittensor.extrinsics.mev_shield import (
extract_mev_shield_id,
wait_for_extrinsic_by_hash,
)
from rich.live import Live
Expand Down Expand Up @@ -272,11 +271,9 @@ async def _find_event_attributes_in_extrinsic_receipt(
# Check for MEV shield execution
if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(response)
mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=response.block_hash,
status=status,
)
Expand Down
21 changes: 15 additions & 6 deletions bittensor_cli/src/commands/wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
NeuronInfoLite,
)
from bittensor_cli.src.bittensor.extrinsics.mev_shield import (
extract_mev_shield_id,
wait_for_extrinsic_by_hash,
)
from bittensor_cli.src.bittensor.extrinsics.registration import (
Expand Down Expand Up @@ -2176,11 +2175,9 @@ async def announce_coldkey_swap(

if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(ext_receipt)
mev_success, mev_error, ext_receipt = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=ext_receipt.block_hash,
status=status,
)
Expand Down Expand Up @@ -2307,7 +2304,7 @@ async def dispute_coldkey_swap(
if not unlock_key(wallet).success:
return False

with console.status(":satellite: Disputing coldkey swap on-chain..."):
with console.status(":satellite: Disputing coldkey swap on-chain...") as status:
call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="dispute_coldkey_swap",
Expand All @@ -2325,6 +2322,20 @@ async def dispute_coldkey_swap(
print_error(f"Failed to dispute coldkey swap: {err_msg}")
return False

if mev_protection:
inner_hash = err_msg
mev_success, mev_error, ext_receipt = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
submit_block_hash=ext_receipt.block_hash,
status=status,
)
if not mev_success:
print_error(
f"Failed to dispute coldkey swap: {mev_error}", status=status
)
return False

print_success("[dark_sea_green3]Coldkey swap disputed.")
await print_extrinsic_id(ext_receipt)

Expand Down Expand Up @@ -2458,11 +2469,9 @@ async def execute_coldkey_swap(

if mev_protection:
inner_hash = err_msg
mev_shield_id = await extract_mev_shield_id(ext_receipt)
mev_success, mev_error, ext_receipt = await wait_for_extrinsic_by_hash(
subtensor=subtensor,
extrinsic_hash=inner_hash,
shield_id=mev_shield_id,
submit_block_hash=ext_receipt.block_hash,
status=status,
)
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ classifiers = [
]
dependencies = [
"wheel",
"async-substrate-interface>=1.6.0",
"async-substrate-interface>=1.6.2",
"aiohttp~=3.13",
"backoff~=2.2.1",
"bittensor-drand>=1.2.0",
"bittensor-drand>=1.3.0",
"GitPython>=3.0.0",
"netaddr~=1.3.0",
"numpy>=2.0.1,<3.0.0",
Expand Down
Loading