From 46a21fb9bea5097dc05b3ffac5947ba461bfe999 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 6 Oct 2025 20:34:26 -0400 Subject: [PATCH 1/6] change legacy (old) runtimeApi params encoding --- async_substrate_interface/async_substrate.py | 4 +++- async_substrate_interface/sync_substrate.py | 4 +++- async_substrate_interface/type_registry.py | 16 +++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index 3ccf8af9..5e258f50 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -3055,7 +3055,9 @@ async def _do_runtime_call_old( param_data = b"" if "encoder" in runtime_call_def: - param_data = runtime_call_def["encoder"](params) + if runtime is None: + runtime = await self.init_runtime(block_hash=block_hash) + param_data = runtime_call_def["encoder"](params, runtime.registry) else: for idx, param in enumerate(runtime_call_def["params"]): param_type_string = f"{param['type']}" diff --git a/async_substrate_interface/sync_substrate.py b/async_substrate_interface/sync_substrate.py index 1622d789..3e8222c0 100644 --- a/async_substrate_interface/sync_substrate.py +++ b/async_substrate_interface/sync_substrate.py @@ -2541,7 +2541,9 @@ def _do_runtime_call_old( param_data = b"" if "encoder" in runtime_call_def: - param_data = runtime_call_def["encoder"](params) + if runtime is None: + runtime = self.init_runtime(block_hash=block_hash) + param_data = runtime_call_def["encoder"](params, runtime.registry) else: for idx, param in enumerate(runtime_call_def["params"]): param_type_string = f"{param['type']}" diff --git a/async_substrate_interface/type_registry.py b/async_substrate_interface/type_registry.py index 0f224e8f..53fcb8b8 100644 --- a/async_substrate_interface/type_registry.py +++ b/async_substrate_interface/type_registry.py @@ -8,7 +8,7 @@ SubnetInfoV2, encode, ) -from scalecodec import ss58_encode +from scalecodec import ss58_decode _TYPE_REGISTRY: dict[str, dict] = { "types": { @@ -24,7 +24,9 @@ "type": "Vec", }, ], - "encoder": lambda addr: encode(ss58_encode(addr), "Vec"), + "encoder": lambda addr, reg: encode( + "Vec", reg, list(bytes.fromhex(ss58_decode(addr))) + ), "type": "Vec", "decoder": DelegateInfo.decode_delegated, }, @@ -97,7 +99,9 @@ }, ], "type": "Vec", - "encoder": lambda addr: encode(ss58_encode(addr), "Vec"), + "encoder": lambda addr, reg: encode( + "Vec", reg, list(bytes.fromhex(ss58_decode(addr))) + ), "decoder": StakeInfo.decode_vec, }, "get_stake_info_for_coldkeys": { @@ -108,8 +112,10 @@ }, ], "type": "Vec", - "encoder": lambda addrs: encode( - [ss58_encode(addr) for addr in addrs], "Vec>" + "encoder": lambda addrs, reg: encode( + "Vec>", + reg, + [list(bytes.fromhex(ss58_decode(addr))) for addr in addrs], ), "decoder": StakeInfo.decode_vec_tuple_vec, }, From e083cfc99a291813712c8ff851c070f1403f9810 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 8 Oct 2025 17:18:51 +0200 Subject: [PATCH 2/6] Small fixes, still WIP --- async_substrate_interface/sync_substrate.py | 3 +-- async_substrate_interface/type_registry.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/async_substrate_interface/sync_substrate.py b/async_substrate_interface/sync_substrate.py index 3e8222c0..f00ea25f 100644 --- a/async_substrate_interface/sync_substrate.py +++ b/async_substrate_interface/sync_substrate.py @@ -2541,8 +2541,7 @@ def _do_runtime_call_old( param_data = b"" if "encoder" in runtime_call_def: - if runtime is None: - runtime = self.init_runtime(block_hash=block_hash) + runtime = self.init_runtime(block_hash=block_hash) param_data = runtime_call_def["encoder"](params, runtime.registry) else: for idx, param in enumerate(runtime_call_def["params"]): diff --git a/async_substrate_interface/type_registry.py b/async_substrate_interface/type_registry.py index 53fcb8b8..53748202 100644 --- a/async_substrate_interface/type_registry.py +++ b/async_substrate_interface/type_registry.py @@ -100,7 +100,7 @@ ], "type": "Vec", "encoder": lambda addr, reg: encode( - "Vec", reg, list(bytes.fromhex(ss58_decode(addr))) + "Vec", reg, list(bytes.fromhex(ss58_decode(addr[0] if isinstance(addr, list) else addr["coldkey"]))) ), "decoder": StakeInfo.decode_vec, }, From 936d6e89b9595180b0ac36c6383feac1e2a5a265 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 8 Oct 2025 17:21:13 +0200 Subject: [PATCH 3/6] Added test (failing currently, needs to pass) --- tests/integration_tests/test_substrate_interface.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/integration_tests/test_substrate_interface.py b/tests/integration_tests/test_substrate_interface.py index ad80401d..0f658649 100644 --- a/tests/integration_tests/test_substrate_interface.py +++ b/tests/integration_tests/test_substrate_interface.py @@ -112,3 +112,12 @@ def test_query_map_with_odd_number_of_params(): first_record = qm.records[0] assert len(first_record) == 2 assert len(first_record[0]) == 4 + + +def test_old_runtime_calls(): + from bittensor import SubtensorApi + sub = SubtensorApi(network='archive', legacy_methods=True, async_subtensor=False) + # will pass + assert sub.get_stake_info_for_coldkey('5CQ6dMW8JZhKCZX9kWsZRqa3kZRKmNHxbPPVFEt6FgyvGv2G', 4943592) + # needs to use legacy + assert sub.get_stake_info_for_coldkey('5CQ6dMW8JZhKCZX9kWsZRqa3kZRKmNHxbPPVFEt6FgyvGv2G', 4670227) From e85cec2ad09e9e10182100a7eb722f490efdbe2d Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 8 Oct 2025 18:12:30 +0200 Subject: [PATCH 4/6] Add Legacy conversion namedtuple --- async_substrate_interface/type_registry.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/async_substrate_interface/type_registry.py b/async_substrate_interface/type_registry.py index 53748202..d292bc96 100644 --- a/async_substrate_interface/type_registry.py +++ b/async_substrate_interface/type_registry.py @@ -1,3 +1,5 @@ +from typing import Union +from collections import namedtuple from bt_decode import ( NeuronInfo, NeuronInfoLite, @@ -10,6 +12,18 @@ ) from scalecodec import ss58_decode + +def stake_info_decode_vec_legacy_compatibility(item) -> list[dict[str, Union[str, int, bytes, bool]]]: + stake_infos: list[StakeInfo] = StakeInfo.decode_vec(item) + NewStakeInfo = namedtuple("NewStakeInfo", ["netuid", "hotkey", "coldkey", "stake", "locked", "emission", "drain", "is_registered"]) + output = [] + for stake_info in stake_infos: + output.append( + NewStakeInfo(0, stake_info.hotkey, stake_info.coldkey, stake_info.stake, 0, 0, 0, False) + ) + return output + + _TYPE_REGISTRY: dict[str, dict] = { "types": { "Balance": "u64", # Need to override default u128 @@ -102,7 +116,7 @@ "encoder": lambda addr, reg: encode( "Vec", reg, list(bytes.fromhex(ss58_decode(addr[0] if isinstance(addr, list) else addr["coldkey"]))) ), - "decoder": StakeInfo.decode_vec, + "decoder": stake_info_decode_vec_legacy_compatibility, }, "get_stake_info_for_coldkeys": { "params": [ From 33a9f181c6b503e456bb2fe158e126aeb678a6b6 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 8 Oct 2025 18:25:27 +0200 Subject: [PATCH 5/6] WIP --- async_substrate_interface/type_registry.py | 55 +++++++++++++++++-- .../test_substrate_interface.py | 11 +++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/async_substrate_interface/type_registry.py b/async_substrate_interface/type_registry.py index d292bc96..7e2e246e 100644 --- a/async_substrate_interface/type_registry.py +++ b/async_substrate_interface/type_registry.py @@ -13,17 +13,52 @@ from scalecodec import ss58_decode -def stake_info_decode_vec_legacy_compatibility(item) -> list[dict[str, Union[str, int, bytes, bool]]]: +def stake_info_decode_vec_legacy_compatibility( + item, +) -> list[dict[str, Union[str, int, bytes, bool]]]: stake_infos: list[StakeInfo] = StakeInfo.decode_vec(item) - NewStakeInfo = namedtuple("NewStakeInfo", ["netuid", "hotkey", "coldkey", "stake", "locked", "emission", "drain", "is_registered"]) + NewStakeInfo = namedtuple( + "NewStakeInfo", + [ + "netuid", + "hotkey", + "coldkey", + "stake", + "locked", + "emission", + "drain", + "is_registered", + ], + ) output = [] for stake_info in stake_infos: output.append( - NewStakeInfo(0, stake_info.hotkey, stake_info.coldkey, stake_info.stake, 0, 0, 0, False) + NewStakeInfo( + 0, + stake_info.hotkey, + stake_info.coldkey, + stake_info.stake, + 0, + 0, + 0, + False, + ) ) return output +def preprocess_get_stake_info_for_coldkeys(addrs): + output = [] + if isinstance(addrs[0], list): # I think + for addr in addrs[0]: + output.append(list(bytes.fromhex(ss58_decode(addr)))) + else: + if isinstance(addrs[0], dict): + for addr in addrs[0]["coldkey_accounts"]: + output.append(list(bytes.fromhex(ss58_decode(addr)))) + return output + + _TYPE_REGISTRY: dict[str, dict] = { "types": { "Balance": "u64", # Need to override default u128 @@ -114,7 +149,17 @@ def stake_info_decode_vec_legacy_compatibility(item) -> list[dict[str, Union[str ], "type": "Vec", "encoder": lambda addr, reg: encode( - "Vec", reg, list(bytes.fromhex(ss58_decode(addr[0] if isinstance(addr, list) else addr["coldkey"]))) + "Vec", + reg, + list( + bytes.fromhex( + ss58_decode( + addr[0] + if isinstance(addr, list) + else addr["coldkey_account"] + ) + ) + ), ), "decoder": stake_info_decode_vec_legacy_compatibility, }, @@ -129,7 +174,7 @@ def stake_info_decode_vec_legacy_compatibility(item) -> list[dict[str, Union[str "encoder": lambda addrs, reg: encode( "Vec>", reg, - [list(bytes.fromhex(ss58_decode(addr))) for addr in addrs], + preprocess_get_stake_info_for_coldkeys(addrs), ), "decoder": StakeInfo.decode_vec_tuple_vec, }, diff --git a/tests/integration_tests/test_substrate_interface.py b/tests/integration_tests/test_substrate_interface.py index 0f658649..2fc8d0f2 100644 --- a/tests/integration_tests/test_substrate_interface.py +++ b/tests/integration_tests/test_substrate_interface.py @@ -116,8 +116,13 @@ def test_query_map_with_odd_number_of_params(): def test_old_runtime_calls(): from bittensor import SubtensorApi - sub = SubtensorApi(network='archive', legacy_methods=True, async_subtensor=False) + + sub = SubtensorApi(network="archive", legacy_methods=True, async_subtensor=False) # will pass - assert sub.get_stake_info_for_coldkey('5CQ6dMW8JZhKCZX9kWsZRqa3kZRKmNHxbPPVFEt6FgyvGv2G', 4943592) + assert sub.get_stake_info_for_coldkey( + "5CQ6dMW8JZhKCZX9kWsZRqa3kZRKmNHxbPPVFEt6FgyvGv2G", 4943592 + ) # needs to use legacy - assert sub.get_stake_info_for_coldkey('5CQ6dMW8JZhKCZX9kWsZRqa3kZRKmNHxbPPVFEt6FgyvGv2G', 4670227) + assert sub.get_stake_info_for_coldkey( + "5CQ6dMW8JZhKCZX9kWsZRqa3kZRKmNHxbPPVFEt6FgyvGv2G", 4670227 + ) From 1b8143fee036514c689ebcb4cb347ed15d77ae3d Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 20 Jan 2026 15:37:11 +0200 Subject: [PATCH 6/6] Checkin --- async_substrate_interface/sync_substrate.py | 26 +++++++-------------- async_substrate_interface/types.py | 17 ++++++++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/async_substrate_interface/sync_substrate.py b/async_substrate_interface/sync_substrate.py index aac8a760..affc773a 100644 --- a/async_substrate_interface/sync_substrate.py +++ b/async_substrate_interface/sync_substrate.py @@ -2527,29 +2527,21 @@ def _do_runtime_call_old( runtime_call_def = _TYPE_REGISTRY["runtime_api"][api]["methods"][method] # Encode params - param_data = b"" + param_data: Union[ScaleBytes, bytes] = b"" + + runtime = self.init_runtime(block_hash=block_hash) - if "encoder" in runtime_call_def: - runtime = self.init_runtime(block_hash=block_hash) + if "encoder" in runtime_call_def and runtime.registry is not None: + # only works if we have metadata v15 param_data = runtime_call_def["encoder"](params, runtime.registry) + param_hex = param_data.hex() else: - for idx, param in enumerate(runtime_call_def["params"]): - param_type_string = f"{param['type']}" - if isinstance(params, list): - param_data += self.encode_scale(param_type_string, params[idx]) - else: - if param["name"] not in params: - raise ValueError( - f"Runtime Call param '{param['name']}' is missing" - ) - - param_data += self.encode_scale( - param_type_string, params[param["name"]] - ) + param_data = self._encode_scale_legacy(runtime_call_def, params, runtime) + param_hex = param_data.to_hex() # RPC request result_data = self.rpc_request( - "state_call", [f"{api}_{method}", param_data.hex(), block_hash] + "state_call", [f"{api}_{method}", param_hex, block_hash] ) result_vec_u8_bytes = hex_to_bytes(result_data["result"]) result_bytes = self.decode_scale("Vec", result_vec_u8_bytes) diff --git a/async_substrate_interface/types.py b/async_substrate_interface/types.py index b9fbfae7..931b6c9e 100644 --- a/async_substrate_interface/types.py +++ b/async_substrate_interface/types.py @@ -965,6 +965,23 @@ def _encode_scale( result = bytes(encode_by_type_string(type_string, runtime.registry, value)) return result + @staticmethod + def _encode_scale_legacy(call_definition: list[dict], params: Union[list[Any], dict[str, Any]], runtime:Runtime) -> bytes: + """Returns a hex encoded string of the params using their types.""" + param_data = scalecodec.ScaleBytes(b"") + + for i, param in enumerate(call_definition["params"]): # type: ignore + scale_obj = runtime.runtime_config.create_scale_object(param["type"]) + if type(params) is list: + param_data += scale_obj.encode(params[i]) + else: + if param["name"] not in params: + raise ValueError(f"Missing param {param['name']} in params dict.") + + param_data += scale_obj.encode(params[param["name"]]) + + return param_data + @staticmethod def _encode_account_id(account) -> bytes: """Encode an account ID into bytes.