Skip to content

Commit 01f4e71

Browse files
committed
Apply legacy v14 support to sync substrate
1 parent 9564406 commit 01f4e71

File tree

5 files changed

+96
-49
lines changed

5 files changed

+96
-49
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
ExtrinsicNotFound,
4141
BlockNotFound,
4242
MaxRetriesExceeded,
43-
MetadataAtVersionNotFound,
4443
StateDiscardedError,
4544
)
4645
from async_substrate_interface.protocols import Keypair
@@ -923,10 +922,6 @@ async def _load_registry_at_block(
923922
"Client error: Execution failed: Other: Exported method Metadata_metadata_at_version is not found"
924923
in e.args
925924
):
926-
logger.warning(
927-
"Exported method Metadata_metadata_at_version is not found. This indicates the block is quite old, "
928-
"decoding for this block will use legacy Python decoding."
929-
)
930925
return None, None
931926
else:
932927
raise e
@@ -1091,9 +1086,15 @@ async def _get_runtime_for_version(
10911086
raise SubstrateRequestException(
10921087
f"No metadata for block '{runtime_block_hash}'"
10931088
)
1094-
logger.debug(
1095-
f"Retrieved metadata and metadata v15 for {runtime_version} from Substrate node"
1096-
)
1089+
if metadata_v15 is not None:
1090+
logger.debug(
1091+
f"Retrieved metadata and metadata v15 for {runtime_version} from Substrate node"
1092+
)
1093+
else:
1094+
logger.debug(
1095+
f"Exported method Metadata_metadata_at_version is not found for {runtime_version}. This indicates the "
1096+
f"block is quite old, decoding for this block will use legacy Python decoding."
1097+
)
10971098
implements_scale_info = metadata.portable_registry is not None
10981099
runtime = Runtime(
10991100
chain=self.chain,
@@ -2921,7 +2922,7 @@ async def _do_runtime_call_old(
29212922
result_vec_u8_bytes = hex_to_bytes(result_data["result"])
29222923
result_bytes = await self.decode_scale(
29232924
"Vec<u8>", result_vec_u8_bytes, runtime=runtime
2924-
) # TODO may need to force_legacy after testing.
2925+
)
29252926

29262927
# Decode result
29272928
# Get correct type

async_substrate_interface/sync_substrate.py

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
SubstrateRequestException,
2424
BlockNotFound,
2525
MaxRetriesExceeded,
26-
MetadataAtVersionNotFound,
2726
StateDiscardedError,
2827
)
2928
from async_substrate_interface.protocols import Keypair
@@ -45,6 +44,7 @@
4544
_determine_if_old_runtime_call,
4645
_bt_decode_to_dict_or_list,
4746
decode_query_map,
47+
legacy_scale_decode,
4848
)
4949
from async_substrate_interface.utils.storage import StorageKey
5050
from async_substrate_interface.type_registry import _TYPE_REGISTRY
@@ -576,17 +576,6 @@ def metadata(self):
576576
else:
577577
return self.runtime.metadata
578578

579-
@property
580-
def implements_scaleinfo(self) -> Optional[bool]:
581-
"""
582-
Returns True if current runtime implements a `PortableRegistry` (`MetadataV14` and higher). Returns `None` if
583-
no currently loaded runtime.
584-
"""
585-
if self.runtime and self.runtime.metadata:
586-
return self.runtime.metadata.portable_registry is not None
587-
else:
588-
return None
589-
590579
@property
591580
def properties(self):
592581
if self._properties is None:
@@ -669,7 +658,7 @@ def _load_registry_at_block(self, block_hash: Optional[str]) -> MetadataV15:
669658
"Client error: Execution failed: Other: Exported method Metadata_metadata_at_version is not found"
670659
in e.args
671660
):
672-
raise MetadataAtVersionNotFound
661+
return None, None
673662
else:
674663
raise e
675664
metadata_option_hex_str = metadata_rpc_result["result"]
@@ -701,7 +690,12 @@ def decode_scale(
701690
# Decode AccountId bytes to SS58 address
702691
return ss58_encode(scale_bytes, SS58_FORMAT)
703692
else:
704-
obj = decode_by_type_string(type_string, self.runtime.registry, scale_bytes)
693+
if self.runtime.metadata_v15 is not None:
694+
obj = decode_by_type_string(
695+
type_string, self.runtime.registry, scale_bytes
696+
)
697+
else:
698+
obj = legacy_scale_decode(type_string, scale_bytes, self.runtime)
705699
if return_scale_obj:
706700
return ScaleObj(obj)
707701
else:
@@ -714,7 +708,7 @@ def load_runtime(self, runtime):
714708
self.runtime.reload_type_registry(use_remote_preset=False, auto_discover=True)
715709

716710
self.runtime_config.set_active_spec_version_id(runtime.runtime_version)
717-
if self.implements_scaleinfo:
711+
if self.runtime.implements_scaleinfo:
718712
logger.debug("Add PortableRegistry from metadata to type registry")
719713
self.runtime_config.add_portable_registry(runtime.metadata)
720714
# Set runtime compatibility flags
@@ -754,7 +748,8 @@ def init_runtime(
754748
if runtime := self.runtime_cache.retrieve(block=block_id):
755749
self.runtime = runtime
756750
self.runtime.load_runtime()
757-
self.runtime.load_registry_type_map()
751+
if self.runtime.registry:
752+
self.runtime.load_registry_type_map()
758753
return self.runtime
759754
block_hash = self.get_block_hash(block_id)
760755

@@ -765,7 +760,8 @@ def init_runtime(
765760
if runtime := self.runtime_cache.retrieve(block_hash=block_hash):
766761
self.runtime = runtime
767762
self.runtime.load_runtime()
768-
self.runtime.load_registry_type_map()
763+
if self.runtime.registry:
764+
self.runtime.load_registry_type_map()
769765
return self.runtime
770766

771767
runtime_version = self.get_block_runtime_version_for(block_hash)
@@ -780,12 +776,14 @@ def init_runtime(
780776
if runtime := self.runtime_cache.retrieve(runtime_version=runtime_version):
781777
self.runtime = runtime
782778
self.runtime.load_runtime()
783-
self.runtime.load_registry_type_map()
779+
if self.runtime.registry:
780+
self.runtime.load_registry_type_map()
784781
return runtime
785782
else:
786783
self.runtime = self.get_runtime_for_version(runtime_version, block_hash)
787784
self.runtime.load_runtime()
788-
self.runtime.load_registry_type_map()
785+
if self.runtime.registry:
786+
self.runtime.load_registry_type_map()
789787
return self.runtime
790788

791789
def get_runtime_for_version(
@@ -819,9 +817,15 @@ def get_runtime_for_version(
819817
metadata_v15, registry = self._load_registry_at_block(
820818
block_hash=runtime_block_hash
821819
)
822-
logger.debug(
823-
f"Retrieved metadata v15 for {runtime_version} from Substrate node"
824-
)
820+
if metadata_v15 is not None:
821+
logger.debug(
822+
f"Retrieved metadata and metadata v15 for {runtime_version} from Substrate node"
823+
)
824+
else:
825+
logger.debug(
826+
f"Exported method Metadata_metadata_at_version is not found for {runtime_version}. This indicates the "
827+
f"block is quite old, decoding for this block will use legacy Python decoding."
828+
)
825829

826830
runtime = Runtime(
827831
chain=self.chain,
@@ -1204,7 +1208,7 @@ def decode_block(block_data, block_data_hash=None) -> dict[str, Any]:
12041208
block_data["header"]["digest"]["logs"][idx] = log_digest
12051209

12061210
if include_author and "PreRuntime" in log_digest.value:
1207-
if self.implements_scaleinfo:
1211+
if self.runtime.implements_scaleinfo:
12081212
engine = bytes(log_digest[1][0])
12091213
# Retrieve validator set
12101214
parent_hash = block_data["header"]["parentHash"]
@@ -1609,7 +1613,12 @@ def convert_event_data(data):
16091613
)
16101614
if storage_obj:
16111615
for item in list(storage_obj):
1612-
events.append(convert_event_data(item))
1616+
try:
1617+
events.append(convert_event_data(item))
1618+
except (
1619+
AttributeError
1620+
): # indicates this was legacy decoded with scalecodec
1621+
events.append(item)
16131622
return events
16141623

16151624
def get_metadata(self, block_hash=None) -> MetadataV15:
@@ -2530,20 +2539,28 @@ def runtime_call(
25302539
params = {}
25312540

25322541
try:
2533-
metadata_v15_value = runtime.metadata_v15.value()
2542+
if runtime.metadata_v15 is None:
2543+
_ = self.runtime_config.type_registry["runtime_api"][api]["methods"][
2544+
method
2545+
]
2546+
runtime_api_types = self.runtime_config.type_registry["runtime_api"][
2547+
api
2548+
].get("types", {})
2549+
runtime.runtime_config.update_type_registry_types(runtime_api_types)
2550+
return self._do_runtime_call_old(api, method, params, block_hash)
2551+
else:
2552+
metadata_v15_value = runtime.metadata_v15.value()
2553+
2554+
apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]}
2555+
api_entry = apis[api]
2556+
methods = {entry["name"]: entry for entry in api_entry["methods"]}
2557+
runtime_call_def = methods[method]
2558+
if _determine_if_old_runtime_call(runtime_call_def, metadata_v15_value):
2559+
return self._do_runtime_call_old(api, method, params, block_hash)
25342560

2535-
apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]}
2536-
api_entry = apis[api]
2537-
methods = {entry["name"]: entry for entry in api_entry["methods"]}
2538-
runtime_call_def = methods[method]
25392561
except KeyError:
25402562
raise ValueError(f"Runtime API Call '{api}.{method}' not found in registry")
25412563

2542-
if _determine_if_old_runtime_call(runtime_call_def, metadata_v15_value):
2543-
result = self._do_runtime_call_old(api, method, params, block_hash)
2544-
2545-
return result
2546-
25472564
if isinstance(params, list) and len(params) != len(runtime_call_def["inputs"]):
25482565
raise ValueError(
25492566
f"Number of parameter provided ({len(params)}) does not "
@@ -2745,7 +2762,7 @@ def get_type_registry(self, block_hash: str = None, max_recursion: int = 4) -> d
27452762
"""
27462763
self.init_runtime(block_hash=block_hash)
27472764

2748-
if not self.implements_scaleinfo:
2765+
if not self.runtime.implements_scaleinfo:
27492766
raise NotImplementedError("MetadataV14 or higher runtimes is required")
27502767

27512768
type_registry = {}

tests/helpers/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@
3232
AURA_NODE_URL = (
3333
environ.get("SUBSTRATE_AURA_NODE_URL") or "wss://acala-rpc-1.aca-api.network"
3434
)
35+
36+
ARCHIVE_ENTRYPOINT = "wss://archive.chain.opentensor.ai:443"

tests/unit_tests/asyncio_/test_substrate_interface.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
from async_substrate_interface.async_substrate import AsyncSubstrateInterface
88
from async_substrate_interface.types import ScaleObj
9-
10-
11-
ARCHIVE_ENTRYPOINT = "wss://archive.chain.opentensor.ai:443"
9+
from tests.helpers.settings import ARCHIVE_ENTRYPOINT
1210

1311

1412
@pytest.mark.asyncio
@@ -137,9 +135,9 @@ async def test_legacy_decoding():
137135
assert isinstance(key, int)
138136
assert isinstance(value, ScaleObj)
139137

140-
unix = await substrate.query(
138+
timestamp = await substrate.query(
141139
"Timestamp",
142140
"Now",
143141
block_hash=block_hash,
144142
)
145-
assert unix.value == 1716358476004
143+
assert timestamp.value == 1716358476004

tests/unit_tests/sync/test_substrate_interface.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from async_substrate_interface.sync_substrate import SubstrateInterface
44
from async_substrate_interface.types import ScaleObj
55

6+
from tests.helpers.settings import ARCHIVE_ENTRYPOINT
7+
68

79
def test_runtime_call(monkeypatch):
810
substrate = SubstrateInterface("ws://localhost", _mock=True)
@@ -72,3 +74,30 @@ def test_runtime_call(monkeypatch):
7274
substrate.rpc_request.assert_any_call(
7375
"state_call", ["SubstrateApi_SubstrateMethod", "", None]
7476
)
77+
substrate.close()
78+
79+
80+
def test_legacy_decoding():
81+
# roughly 4000 blocks before metadata v15 was added
82+
pre_metadata_v15_block = 3_010_611
83+
84+
with SubstrateInterface(ARCHIVE_ENTRYPOINT) as substrate:
85+
block_hash = substrate.get_block_hash(pre_metadata_v15_block)
86+
events = substrate.get_events(block_hash)
87+
assert isinstance(events, list)
88+
89+
query_map_result = substrate.query_map(
90+
module="SubtensorModule",
91+
storage_function="NetworksAdded",
92+
block_hash=block_hash,
93+
)
94+
for key, value in query_map_result:
95+
assert isinstance(key, int)
96+
assert isinstance(value, ScaleObj)
97+
98+
timestamp = substrate.query(
99+
"Timestamp",
100+
"Now",
101+
block_hash=block_hash,
102+
)
103+
assert timestamp.value == 1716358476004

0 commit comments

Comments
 (0)