Skip to content

Commit 470e8ee

Browse files
committed
add async ver
1 parent 606ee2d commit 470e8ee

File tree

1 file changed

+126
-3
lines changed

1 file changed

+126
-3
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@
2424
import asyncstdlib as a
2525
from bittensor_wallet.keypair import Keypair
2626
from bittensor_wallet.utils import SS58_FORMAT
27-
from bt_decode import MetadataV15, PortableRegistry, decode as decode_by_type_string
27+
from bt_decode import (
28+
MetadataV15,
29+
PortableRegistry,
30+
decode as decode_by_type_string,
31+
AxonInfo as OldAxonInfo,
32+
PrometheusInfo as OldPrometheusInfo,
33+
)
2834
from scalecodec.base import ScaleBytes, ScaleType, RuntimeConfigurationObject
2935
from scalecodec.types import (
3036
GenericCall,
@@ -49,7 +55,9 @@
4955
Preprocessed,
5056
)
5157
from async_substrate_interface.utils import hex_to_bytes, json
58+
from async_substrate_interface.utils.decoding import _determine_if_old_runtime_call
5259
from async_substrate_interface.utils.storage import StorageKey
60+
from async_substrate_interface.type_registry import _TYPE_REGISTRY
5361

5462
if TYPE_CHECKING:
5563
from websockets.asyncio.client import ClientConnection
@@ -706,6 +714,8 @@ def __init__(
706714
ss58_format=self.ss58_format, implements_scale_info=True
707715
)
708716
self._metadata_cache = {}
717+
self._metadata_v15_cache = {}
718+
self._old_metadata_v15 = None
709719
self._nonces = {}
710720
self.metadata_version_hex = "0x0f000000" # v15
711721
self.reload_type_registry()
@@ -800,6 +810,20 @@ async def load_registry(self):
800810
)
801811
self.registry = PortableRegistry.from_metadata_v15(self.metadata_v15)
802812

813+
async def _load_registry_at_block(self, block_hash: str) -> MetadataV15:
814+
# Should be called for any block that fails decoding.
815+
# Possibly the metadata was different.
816+
metadata_rpc_result = await self.rpc_request(
817+
"state_call",
818+
["Metadata_metadata_at_version", self.metadata_version_hex],
819+
block_hash=block_hash,
820+
)
821+
metadata_option_hex_str = metadata_rpc_result["result"]
822+
metadata_option_bytes = bytes.fromhex(metadata_option_hex_str[2:])
823+
old_metadata = MetadataV15.decode_from_metadata_option(metadata_option_bytes)
824+
825+
return old_metadata
826+
803827
async def _wait_for_registry(self, _attempt: int = 1, _retries: int = 3) -> None:
804828
async def _waiter():
805829
while self.registry is None:
@@ -890,6 +914,7 @@ async def _first_initialize_runtime(self):
890914
)
891915
self._metadata = metadata
892916
self._metadata_cache[self.runtime_version] = self._metadata
917+
self._metadata_v15_cache[self.runtime_version] = self.metadata_v15
893918
self.runtime_version = runtime_info.get("specVersion")
894919
self.runtime_config.set_active_spec_version_id(self.runtime_version)
895920
self.transaction_version = runtime_info.get("transactionVersion")
@@ -1015,6 +1040,30 @@ async def get_runtime(block_hash, block_id) -> Runtime:
10151040
self._metadata_cache[self.runtime_version] = self._metadata
10161041
else:
10171042
metadata = self._metadata
1043+
1044+
if self.runtime_version in self._metadata_v15_cache:
1045+
# Get metadata v15 from cache
1046+
logging.debug(
1047+
"Retrieved metadata v15 for {} from memory".format(
1048+
self.runtime_version
1049+
)
1050+
)
1051+
metadata_v15 = self._old_metadata_v15 = self._metadata_v15_cache[
1052+
self.runtime_version
1053+
]
1054+
else:
1055+
metadata_v15 = (
1056+
self._old_metadata_v15
1057+
) = await self._load_registry_at_block(block_hash=runtime_block_hash)
1058+
logging.debug(
1059+
"Retrieved metadata v15 for {} from Substrate node".format(
1060+
self.runtime_version
1061+
)
1062+
)
1063+
1064+
# Update metadata v15 cache
1065+
self._metadata_v15_cache[self.runtime_version] = metadata_v15
1066+
10181067
# Update type registry
10191068
self.reload_type_registry(use_remote_preset=False, auto_discover=True)
10201069

@@ -2487,6 +2536,67 @@ async def get_chain_finalised_head(self):
24872536

24882537
return response.get("result")
24892538

2539+
async def _do_runtime_call_old(
2540+
self,
2541+
api: str,
2542+
method: str,
2543+
params: Optional[Union[list, dict]] = None,
2544+
block_hash: Optional[str] = None,
2545+
) -> ScaleType:
2546+
logging.debug(
2547+
f"Decoding old runtime call: {api}.{method} with params: {params} at block hash: {block_hash}"
2548+
)
2549+
runtime_call_def = _TYPE_REGISTRY["runtime_api"][api]["methods"][method]
2550+
2551+
# Encode params
2552+
param_data = b""
2553+
2554+
if "encoder" in runtime_call_def:
2555+
param_data = runtime_call_def["encoder"](params)
2556+
else:
2557+
for idx, param in enumerate(runtime_call_def["params"]):
2558+
param_type_string = f"{param['type']}"
2559+
if isinstance(params, list):
2560+
param_data += await self.encode_scale(
2561+
param_type_string, params[idx]
2562+
)
2563+
else:
2564+
if param["name"] not in params:
2565+
raise ValueError(
2566+
f"Runtime Call param '{param['name']}' is missing"
2567+
)
2568+
2569+
param_data += await self.encode_scale(
2570+
param_type_string, params[param["name"]]
2571+
)
2572+
2573+
# RPC request
2574+
result_data = await self.rpc_request(
2575+
"state_call", [f"{api}_{method}", param_data.hex(), block_hash]
2576+
)
2577+
result_vec_u8_bytes = hex_to_bytes(result_data["result"])
2578+
result_bytes = await self.decode_scale("Vec<u8>", result_vec_u8_bytes)
2579+
2580+
def _as_dict(obj):
2581+
as_dict = {}
2582+
for key in dir(obj):
2583+
if not key.startswith("_"):
2584+
val = getattr(obj, key)
2585+
if isinstance(val, (OldAxonInfo, OldPrometheusInfo)):
2586+
as_dict[key] = _as_dict(val)
2587+
else:
2588+
as_dict[key] = val
2589+
return as_dict
2590+
2591+
# Decode result
2592+
# Get correct type
2593+
result_decoded = runtime_call_def["decoder"](bytes(result_bytes))
2594+
as_dict = _as_dict(result_decoded)
2595+
logging.debug("Decoded old runtime call result: ", as_dict)
2596+
result_obj = ScaleObj(as_dict)
2597+
2598+
return result_obj
2599+
24902600
async def runtime_call(
24912601
self,
24922602
api: str,
@@ -2513,14 +2623,27 @@ async def runtime_call(
25132623
params = {}
25142624

25152625
try:
2516-
metadata_v15 = self.metadata_v15.value()
2517-
apis = {entry["name"]: entry for entry in metadata_v15["apis"]}
2626+
if block_hash:
2627+
# Use old metadata v15 from init_runtime call
2628+
metadata_v15 = self._old_metadata_v15
2629+
else:
2630+
metadata_v15 = self.metadata_v15
2631+
2632+
self.registry = PortableRegistry.from_metadata_v15(metadata_v15)
2633+
metadata_v15_value = metadata_v15.value()
2634+
2635+
apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]}
25182636
api_entry = apis[api]
25192637
methods = {entry["name"]: entry for entry in api_entry["methods"]}
25202638
runtime_call_def = methods[method]
25212639
except KeyError:
25222640
raise ValueError(f"Runtime API Call '{api}.{method}' not found in registry")
25232641

2642+
if _determine_if_old_runtime_call(runtime_call_def, metadata_v15_value):
2643+
result = await self._do_runtime_call_old(api, method, params, block_hash)
2644+
2645+
return result
2646+
25242647
if isinstance(params, list) and len(params) != len(runtime_call_def["inputs"]):
25252648
raise ValueError(
25262649
f"Number of parameter provided ({len(params)}) does not "

0 commit comments

Comments
 (0)