1- import asyncio
21import hashlib
32from typing import TYPE_CHECKING , Optional
43
54from async_substrate_interface import AsyncExtrinsicReceipt
6- from bittensor_drand import encrypt_mlkem768 , mlkem_kdf_id
7- from bittensor_cli .src .bittensor .utils import encode_account_id , format_error_message
5+ from bittensor_drand import encrypt_mlkem768
6+ from bittensor_cli .src .bittensor .utils import format_error_message
87
98if TYPE_CHECKING :
10- from bittensor_wallet import Wallet
11- from scalecodec import GenericCall
9+ from bittensor_wallet import Keypair
10+ from scalecodec import GenericCall , GenericExtrinsic
1211 from bittensor_cli .src .bittensor .subtensor_interface import SubtensorInterface
1312
1413
15- async def encrypt_call (
14+ async def encrypt_extrinsic (
1615 subtensor : "SubtensorInterface" ,
17- wallet : "Wallet" ,
18- call : "GenericCall" ,
16+ signed_extrinsic : "GenericExtrinsic" ,
1917) -> "GenericCall" :
2018 """
21- Encrypt a call using MEV Shield.
19+ Encrypt a signed extrinsic using MEV Shield.
2220
23- Takes any call and returns a MevShield.submit_encrypted call
24- that can be submitted like any regular extrinsic.
21+ Takes a pre-signed extrinsic and returns a MevShield.submit_encrypted call.
2522
2623 Args:
2724 subtensor: The SubtensorInterface instance for chain queries.
28- wallet: The wallet whose coldkey will sign the inner payload.
29- call: The call to encrypt.
25+ signed_extrinsic: The signed extrinsic to encrypt.
3026
3127 Returns:
32- A MevShield.submit_encrypted call.
28+ A MevShield.submit_encrypted call to be signed with the current nonce .
3329
3430 Raises:
3531 ValueError: If MEV Shield NextKey is not available on chain.
3632 """
3733
38- next_key_result , genesis_hash = await asyncio .gather (
39- subtensor .get_mev_shield_next_key (),
40- subtensor .substrate .get_block_hash (0 ),
41- )
42- if next_key_result is None :
34+ ml_kem_768_public_key = await subtensor .get_mev_shield_next_key ()
35+ if ml_kem_768_public_key is None :
4336 raise ValueError ("MEV Shield NextKey not available on chain" )
4437
45- ml_kem_768_public_key = next_key_result
46-
47- # Create payload_core: signer (32B) + next_key (32B) + SCALE(call)
48- signer_bytes = encode_account_id (wallet .coldkey .ss58_address )
49- scale_call_bytes = bytes (call .data .data )
50- next_key = hashlib .blake2b (next_key_result , digest_size = 32 ).digest ()
51-
52- payload_core = signer_bytes + next_key + scale_call_bytes
53-
54- mev_shield_version = mlkem_kdf_id ()
55- genesis_hash_clean = (
56- genesis_hash [2 :] if genesis_hash .startswith ("0x" ) else genesis_hash
57- )
58- genesis_hash_bytes = bytes .fromhex (genesis_hash_clean )
59-
60- # Sign: coldkey.sign(b"mev-shield:v1" + genesis_hash + payload_core)
61- message_to_sign = (
62- b"mev-shield:" + mev_shield_version + genesis_hash_bytes + payload_core
63- )
64- signature = wallet .coldkey .sign (message_to_sign )
65-
66- # Plaintext: payload_core + b"\x01" + signature
67- plaintext = payload_core + b"\x01 " + signature
38+ plaintext = bytes (signed_extrinsic .data .data )
6839
6940 # Encrypt using ML-KEM-768
7041 ciphertext = encrypt_mlkem768 (ml_kem_768_public_key , plaintext )
7142
7243 # Commitment: blake2_256(payload_core)
73- commitment_hash = hashlib .blake2b (payload_core , digest_size = 32 ).digest ()
44+ commitment_hash = hashlib .blake2b (plaintext , digest_size = 32 ).digest ()
7445 commitment_hex = "0x" + commitment_hash .hex ()
7546
7647 # Create the MevShield.submit_encrypted call
@@ -105,31 +76,37 @@ async def extract_mev_shield_id(response: "AsyncExtrinsicReceipt") -> Optional[s
10576 return None
10677
10778
108- async def wait_for_mev_execution (
79+ async def wait_for_extrinsic_by_hash (
10980 subtensor : "SubtensorInterface" ,
110- wrapper_id : str ,
81+ extrinsic_hash : str ,
82+ shield_id : str ,
11183 submit_block_hash : str ,
112- timeout_blocks : int = 4 ,
84+ timeout_blocks : int = 2 ,
11385 status = None ,
11486) -> tuple [bool , Optional [str ], Optional [AsyncExtrinsicReceipt ]]:
11587 """
116- Wait for MEV Shield inner call execution .
88+ Wait for the result of a MeV Shield encrypted extrinsic .
11789
118- After submit_encrypted succeeds, the block author will decrypt and execute
119- the inner call via execute_revealed. This function polls for the
120- DecryptedExecuted or DecryptedRejected event.
90+ After submit_encrypted succeeds, the block author will decrypt and submit
91+ the inner extrinsic directly. This function polls subsequent blocks looking
92+ for either:
93+ - an extrinsic matching the provided hash (success)
94+ OR
95+ - a markDecryptionFailed extrinsic with matching shield ID (failure)
12196
12297 Args:
12398 subtensor: SubtensorInterface instance.
124- wrapper_id: The ID from EncryptedSubmitted event.
125- submit_block_number: Block number where submit_encrypted was included.
126- timeout_blocks: Max blocks to wait (default 4).
99+ extrinsic_hash: The hash of the inner extrinsic to find.
100+ shield_id: The wrapper ID from EncryptedSubmitted event (for detecting decryption failures).
101+ submit_block_hash: Block hash where submit_encrypted was included.
102+ timeout_blocks: Max blocks to wait (default 2).
127103 status: Optional rich.Status object for progress updates.
128104
129105 Returns:
130106 Tuple of (success: bool, error: Optional[str], receipt: Optional[AsyncExtrinsicReceipt]).
131- - (True, None, receipt) if DecryptedExecuted was found.
132- - (False, error_message, None) if the call failed or timeout.
107+ - (True, None, receipt) if extrinsic was found and succeeded.
108+ - (False, error_message, receipt) if extrinsic was found but failed.
109+ - (False, "Timeout...", None) if not found within timeout.
133110 """
134111
135112 async def _noop (_ ):
@@ -154,42 +131,45 @@ async def _noop(_):
154131 block_hash = await subtensor .substrate .get_block_hash (current_block )
155132 extrinsics = await subtensor .substrate .get_extrinsics (block_hash )
156133
157- # Find executeRevealed extrinsic & match ids
158- execute_revealed_index = None
134+ result_idx = None
159135 for idx , extrinsic in enumerate (extrinsics ):
160- call = extrinsic .value .get ("call" , {})
161- call_module = call .get ("call_module" )
162- call_function = call .get ("call_function" )
136+ # Success: Inner extrinsic executed
137+ if f"0x{ extrinsic .extrinsic_hash .hex ()} " == extrinsic_hash :
138+ result_idx = idx
139+ break
163140
164- if call_module == "MevShield" and call_function in (
165- "execute_revealed" ,
166- "mark_decryption_failed" ,
141+ # Failure: Decryption failed
142+ call = extrinsic .value .get ("call" , {})
143+ if (
144+ call .get ("call_module" ) == "MevShield"
145+ and call .get ("call_function" ) == "mark_decryption_failed"
167146 ):
168147 call_args = call .get ("call_args" , [])
169148 for arg in call_args :
170- if arg .get ("name" ) == "id" :
171- extrinsic_wrapper_id = arg .get ("value" )
172- if extrinsic_wrapper_id == wrapper_id :
173- execute_revealed_index = idx
174- break
175-
176- if execute_revealed_index is not None :
149+ if arg .get ("name" ) == "id" and arg .get ("value" ) == shield_id :
150+ result_idx = idx
151+ break
152+ if result_idx is not None :
177153 break
178154
179- if execute_revealed_index is None :
180- current_block += 1
181- continue
155+ if result_idx is not None :
156+ receipt = AsyncExtrinsicReceipt (
157+ substrate = subtensor .substrate ,
158+ block_hash = block_hash ,
159+ block_number = current_block ,
160+ extrinsic_idx = result_idx ,
161+ )
182162
183- receipt = AsyncExtrinsicReceipt (
184- substrate = subtensor .substrate ,
185- block_hash = block_hash ,
186- extrinsic_idx = execute_revealed_index ,
187- )
163+ if not await receipt .is_success :
164+ error_msg = format_error_message (await receipt .error_message )
165+ return False , error_msg , receipt
188166
189- if not await receipt .is_success :
190- error_msg = format_error_message (await receipt .error_message )
191- return False , error_msg , None
167+ return True , None , receipt
192168
193- return True , None , receipt
169+ current_block += 1
194170
195- return False , "Timeout waiting for MEV Shield execution" , None
171+ return (
172+ False ,
173+ "Failed to find outcome of the shield extrinsic (The protected extrinsic wasn't decrypted)" ,
174+ None ,
175+ )
0 commit comments