Skip to content

Commit f254c7e

Browse files
authored
Merge pull request #141 from opentensor/feat/thewhaleking/distribute-runtime
Only use Runtime objects in AsyncSubstrateInterface
2 parents 2d4b558 + fc2a7bd commit f254c7e

File tree

10 files changed

+947
-685
lines changed

10 files changed

+947
-685
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 361 additions & 249 deletions
Large diffs are not rendered by default.

async_substrate_interface/sync_substrate.py

Lines changed: 148 additions & 116 deletions
Large diffs are not rendered by default.

async_substrate_interface/types.py

Lines changed: 338 additions & 311 deletions
Large diffs are not rendered by default.

async_substrate_interface/utils/decoding.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Union, TYPE_CHECKING
22

33
from bt_decode import AxonInfo, PrometheusInfo, decode_list
4+
from scalecodec import ScaleBytes
45

56
from async_substrate_interface.utils import hex_to_bytes
67
from async_substrate_interface.types import ScaleObj
@@ -55,10 +56,16 @@ def _bt_decode_to_dict_or_list(obj) -> Union[dict, list[dict]]:
5556
def _decode_scale_list_with_runtime(
5657
type_strings: list[str],
5758
scale_bytes_list: list[bytes],
58-
runtime_registry,
59+
runtime: "Runtime",
5960
return_scale_obj: bool = False,
6061
):
61-
obj = decode_list(type_strings, runtime_registry, scale_bytes_list)
62+
if runtime.metadata_v15 is not None:
63+
obj = decode_list(type_strings, runtime.registry, scale_bytes_list)
64+
else:
65+
obj = [
66+
legacy_scale_decode(x, y, runtime)
67+
for (x, y) in zip(type_strings, scale_bytes_list)
68+
]
6269
if return_scale_obj:
6370
return [ScaleObj(x) for x in obj]
6471
else:
@@ -109,7 +116,7 @@ def concat_hash_len(key_hasher: str) -> int:
109116
all_decoded = _decode_scale_list_with_runtime(
110117
pre_decoded_key_types + pre_decoded_value_types,
111118
pre_decoded_keys + pre_decoded_values,
112-
runtime.registry,
119+
runtime,
113120
)
114121
middl_index = len(all_decoded) // 2
115122
decoded_keys = all_decoded[:middl_index]
@@ -132,3 +139,18 @@ def concat_hash_len(key_hasher: str) -> int:
132139
item_value = dv
133140
result.append([item_key, item_value])
134141
return result
142+
143+
144+
def legacy_scale_decode(
145+
type_string: str, scale_bytes: Union[str, ScaleBytes], runtime: "Runtime"
146+
):
147+
if isinstance(scale_bytes, (str, bytes)):
148+
scale_bytes = ScaleBytes(scale_bytes)
149+
150+
obj = runtime.runtime_config.create_scale_object(
151+
type_string=type_string, data=scale_bytes, metadata=runtime.metadata
152+
)
153+
154+
obj.decode(check_remaining=runtime.config.get("strict_scale_decode"))
155+
156+
return obj.value

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"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import pytest
2+
3+
from async_substrate_interface.async_substrate import AsyncSubstrateInterface
4+
from async_substrate_interface.types import ScaleObj
5+
from tests.helpers.settings import ARCHIVE_ENTRYPOINT
6+
7+
8+
@pytest.mark.asyncio
9+
async def test_legacy_decoding():
10+
# roughly 4000 blocks before metadata v15 was added
11+
pre_metadata_v15_block = 3_010_611
12+
13+
async with AsyncSubstrateInterface(ARCHIVE_ENTRYPOINT) as substrate:
14+
block_hash = await substrate.get_block_hash(pre_metadata_v15_block)
15+
events = await substrate.get_events(block_hash)
16+
assert isinstance(events, list)
17+
18+
query_map_result = await substrate.query_map(
19+
module="SubtensorModule",
20+
storage_function="NetworksAdded",
21+
block_hash=block_hash,
22+
)
23+
async for key, value in query_map_result:
24+
assert isinstance(key, int)
25+
assert isinstance(value, ScaleObj)
26+
27+
timestamp = await substrate.query(
28+
"Timestamp",
29+
"Now",
30+
block_hash=block_hash,
31+
)
32+
assert timestamp.value == 1716358476004
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from async_substrate_interface.sync_substrate import SubstrateInterface
2+
from async_substrate_interface.types import ScaleObj
3+
from tests.helpers.settings import ARCHIVE_ENTRYPOINT
4+
5+
6+
def test_legacy_decoding():
7+
# roughly 4000 blocks before metadata v15 was added
8+
pre_metadata_v15_block = 3_010_611
9+
10+
with SubstrateInterface(ARCHIVE_ENTRYPOINT) as substrate:
11+
block_hash = substrate.get_block_hash(pre_metadata_v15_block)
12+
events = substrate.get_events(block_hash)
13+
assert isinstance(events, list)
14+
15+
query_map_result = substrate.query_map(
16+
module="SubtensorModule",
17+
storage_function="NetworksAdded",
18+
block_hash=block_hash,
19+
)
20+
for key, value in query_map_result:
21+
assert isinstance(key, int)
22+
assert isinstance(value, ScaleObj)
23+
24+
timestamp = substrate.query(
25+
"Timestamp",
26+
"Now",
27+
block_hash=block_hash,
28+
)
29+
assert timestamp.value == 1716358476004

tests/unit_tests/asyncio_/test_substrate_interface.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import asyncio
2-
from unittest.mock import AsyncMock, MagicMock
2+
from unittest.mock import AsyncMock, MagicMock, ANY
33

44
import pytest
55
from websockets.exceptions import InvalidURI
@@ -64,7 +64,7 @@ async def test_runtime_call(monkeypatch):
6464

6565
# Patch RPC request with correct behavior
6666
substrate.rpc_request = AsyncMock(
67-
side_effect=lambda method, params: {
67+
side_effect=lambda method, params, runtime: {
6868
"result": "0x00" if method == "state_call" else {"parentHash": "0xDEADBEEF"}
6969
}
7070
)
@@ -83,14 +83,16 @@ async def test_runtime_call(monkeypatch):
8383
assert result.value == "decoded_result"
8484

8585
# Check decode_scale called correctly
86-
substrate.decode_scale.assert_called_once_with("scale_info::1", b"\x00")
86+
substrate.decode_scale.assert_called_once_with(
87+
"scale_info::1", b"\x00", runtime=ANY
88+
)
8789

8890
# encode_scale should not be called since no inputs
8991
substrate.encode_scale.assert_not_called()
9092

9193
# Check RPC request called for the state_call
9294
substrate.rpc_request.assert_any_call(
93-
"state_call", ["SubstrateApi_SubstrateMethod", "", None]
95+
"state_call", ["SubstrateApi_SubstrateMethod", "", None], runtime=ANY
9496
)
9597

9698

tests/unit_tests/sync/test_substrate_interface.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,4 @@ def test_runtime_call(monkeypatch):
7272
substrate.rpc_request.assert_any_call(
7373
"state_call", ["SubstrateApi_SubstrateMethod", "", None]
7474
)
75+
substrate.close()

tests/unit_tests/test_cache.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@ async def error_method(x):
7171
@pytest.mark.asyncio
7272
async def test_cached_fetcher_eviction():
7373
"""Tests that LRU eviction works in CachedFetcher."""
74-
mock_method = mock.AsyncMock(side_effect=lambda x: f"val_{x}")
75-
fetcher = CachedFetcher(max_size=2, method=mock_method)
74+
75+
async def side_effect_method(x):
76+
return f"val_{x}"
77+
78+
fetcher = CachedFetcher(max_size=2, method=side_effect_method)
7679

7780
# Fill cache
7881
await fetcher("key1")

0 commit comments

Comments
 (0)