Skip to content

Commit bd870cf

Browse files
authored
Merge pull request #145 from opentensor/feat/thewhaleking/only-use-v14-decoding-for-events
Only use v14 decoding for events
2 parents 35728ab + be318fd commit bd870cf

File tree

4 files changed

+101
-32
lines changed

4 files changed

+101
-32
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -281,17 +281,26 @@ async def process_events(self):
281281
self.__weight = dispatch_info["weight"]
282282

283283
if "Module" in dispatch_error:
284-
module_index = dispatch_error["Module"][0]["index"]
285-
error_index = int.from_bytes(
286-
bytes(dispatch_error["Module"][0]["error"]),
287-
byteorder="little",
288-
signed=False,
289-
)
284+
if isinstance(dispatch_error["Module"], tuple):
285+
module_index = dispatch_error["Module"][0]
286+
error_index = dispatch_error["Module"][1]
287+
else:
288+
module_index = dispatch_error["Module"]["index"]
289+
error_index = dispatch_error["Module"]["error"]
290290

291291
if isinstance(error_index, str):
292292
# Actual error index is first u8 in new [u8; 4] format
293293
error_index = int(error_index[2:4], 16)
294-
module_error = self.substrate.metadata.get_module_error(
294+
295+
if self.block_hash:
296+
runtime = await self.substrate.init_runtime(
297+
block_hash=self.block_hash
298+
)
299+
else:
300+
runtime = await self.substrate.init_runtime(
301+
block_id=self.block_number
302+
)
303+
module_error = runtime.metadata.get_module_error(
295304
module_index=module_index, error_index=error_index
296305
)
297306
self.__error_message = {
@@ -823,6 +832,7 @@ async def initialize(self):
823832
if ss58_prefix_constant:
824833
self.ss58_format = ss58_prefix_constant.value
825834
runtime.ss58_format = ss58_prefix_constant.value
835+
runtime.runtime_config.ss58_format = ss58_prefix_constant.value
826836
self.initialized = True
827837
self._initializing = False
828838

@@ -999,7 +1009,7 @@ async def decode_scale(
9991009
else:
10001010
if not runtime:
10011011
runtime = await self.init_runtime(block_hash=block_hash)
1002-
if runtime.metadata_v15 is not None or force_legacy is True:
1012+
if runtime.metadata_v15 is not None and force_legacy is False:
10031013
obj = decode_by_type_string(type_string, runtime.registry, scale_bytes)
10041014
if self.decode_ss58:
10051015
try:
@@ -1930,7 +1940,13 @@ def convert_event_data(data):
19301940
if key == "who":
19311941
who = ss58_encode(bytes(value[0]), self.ss58_format)
19321942
attributes["who"] = who
1933-
if isinstance(value, dict):
1943+
elif key == "from":
1944+
who_from = ss58_encode(bytes(value[0]), self.ss58_format)
1945+
attributes["from"] = who_from
1946+
elif key == "to":
1947+
who_to = ss58_encode(bytes(value[0]), self.ss58_format)
1948+
attributes["to"] = who_to
1949+
elif isinstance(value, dict):
19341950
# Convert nested single-key dictionaries to their keys as strings
19351951
for sub_key, sub_value in value.items():
19361952
if isinstance(sub_value, dict):
@@ -1958,16 +1974,15 @@ def convert_event_data(data):
19581974
block_hash = await self.get_chain_head()
19591975

19601976
storage_obj = await self.query(
1961-
module="System", storage_function="Events", block_hash=block_hash
1977+
module="System",
1978+
storage_function="Events",
1979+
block_hash=block_hash,
1980+
force_legacy_decode=True,
19621981
)
1982+
# bt-decode Metadata V15 is not ideal for events. Force legacy decoding for this
19631983
if storage_obj:
19641984
for item in list(storage_obj):
1965-
try:
1966-
events.append(convert_event_data(item))
1967-
except (
1968-
AttributeError
1969-
): # indicates this was legacy decoded with scalecodec
1970-
events.append(item)
1985+
events.append(item)
19711986
return events
19721987

19731988
async def get_metadata(self, block_hash=None) -> MetadataV15:
@@ -2175,6 +2190,7 @@ async def _process_response(
21752190
storage_item: Optional[ScaleType] = None,
21762191
result_handler: Optional[ResultHandler] = None,
21772192
runtime: Optional[Runtime] = None,
2193+
force_legacy_decode: bool = False,
21782194
) -> tuple[Any, bool]:
21792195
"""
21802196
Processes the RPC call response by decoding it, returning it as is, or setting a handler for subscriptions,
@@ -2187,6 +2203,7 @@ async def _process_response(
21872203
storage_item: The ScaleType object used for decoding ScaleBytes results
21882204
result_handler: the result handler coroutine used for handling longer-running subscriptions
21892205
runtime: Optional Runtime to use for decoding. If not specified, the currently-loaded `self.runtime` is used
2206+
force_legacy_decode: Whether to force the use of the legacy Metadata V14 decoder
21902207
21912208
Returns:
21922209
(decoded response, completion)
@@ -2208,7 +2225,9 @@ async def _process_response(
22082225
q = bytes(query_value)
22092226
else:
22102227
q = query_value
2211-
result = await self.decode_scale(value_scale_type, q, runtime=runtime)
2228+
result = await self.decode_scale(
2229+
value_scale_type, q, runtime=runtime, force_legacy=force_legacy_decode
2230+
)
22122231
if asyncio.iscoroutinefunction(result_handler):
22132232
# For multipart responses as a result of subscriptions.
22142233
message, bool_result = await result_handler(result, subscription_id)
@@ -2223,6 +2242,7 @@ async def _make_rpc_request(
22232242
result_handler: Optional[ResultHandler] = None,
22242243
attempt: int = 1,
22252244
runtime: Optional[Runtime] = None,
2245+
force_legacy_decode: bool = False,
22262246
) -> RequestManager.RequestResults:
22272247
request_manager = RequestManager(payloads)
22282248

@@ -2267,6 +2287,7 @@ async def _make_rpc_request(
22672287
storage_item,
22682288
result_handler,
22692289
runtime=runtime,
2290+
force_legacy_decode=force_legacy_decode,
22702291
)
22712292

22722293
request_manager.add_response(
@@ -2298,6 +2319,7 @@ async def _make_rpc_request(
22982319
storage_item,
22992320
result_handler,
23002321
attempt + 1,
2322+
force_legacy_decode,
23012323
)
23022324

23032325
return request_manager.get_results()
@@ -3323,6 +3345,7 @@ async def query(
33233345
subscription_handler=None,
33243346
reuse_block_hash: bool = False,
33253347
runtime: Optional[Runtime] = None,
3348+
force_legacy_decode: bool = False,
33263349
) -> Optional[Union["ScaleObj", Any]]:
33273350
"""
33283351
Queries substrate. This should only be used when making a single request. For multiple requests,
@@ -3355,6 +3378,7 @@ async def query(
33553378
storage_item,
33563379
result_handler=subscription_handler,
33573380
runtime=runtime,
3381+
force_legacy_decode=force_legacy_decode,
33583382
)
33593383
result = responses[preprocessed.queryable][0]
33603384
if isinstance(result, (list, tuple, int, float)):

async_substrate_interface/sync_substrate.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -256,16 +256,17 @@ def process_events(self):
256256
self.__weight = dispatch_info["weight"]
257257

258258
if "Module" in dispatch_error:
259-
module_index = dispatch_error["Module"][0]["index"]
260-
error_index = int.from_bytes(
261-
bytes(dispatch_error["Module"][0]["error"]),
262-
byteorder="little",
263-
signed=False,
264-
)
259+
if isinstance(dispatch_error["Module"], tuple):
260+
module_index = dispatch_error["Module"][0]
261+
error_index = dispatch_error["Module"][1]
262+
else:
263+
module_index = dispatch_error["Module"]["index"]
264+
error_index = dispatch_error["Module"]["error"]
265265

266266
if isinstance(error_index, str):
267267
# Actual error index is first u8 in new [u8; 4] format
268268
error_index = int(error_index[2:4], 16)
269+
269270
module_error = self.substrate.metadata.get_module_error(
270271
module_index=module_index, error_index=error_index
271272
)
@@ -568,6 +569,7 @@ def initialize(self):
568569
if ss58_prefix_constant:
569570
self.ss58_format = ss58_prefix_constant.value
570571
self.runtime.ss58_format = ss58_prefix_constant.value
572+
self.runtime.runtime_config.ss58_format = ss58_prefix_constant.value
571573
self.initialized = True
572574

573575
def __exit__(self, exc_type, exc_val, exc_tb):
@@ -679,6 +681,7 @@ def decode_scale(
679681
type_string: str,
680682
scale_bytes: bytes,
681683
return_scale_obj=False,
684+
force_legacy: bool = False,
682685
) -> Union[ScaleObj, Any]:
683686
"""
684687
Helper function to decode arbitrary SCALE-bytes (e.g. 0x02000000) according to given RUST type_string
@@ -689,6 +692,7 @@ def decode_scale(
689692
type_string: the type string of the SCALE object for decoding
690693
scale_bytes: the bytes representation of the SCALE object to decode
691694
return_scale_obj: Whether to return the decoded value wrapped in a SCALE-object-like wrapper, or raw.
695+
force_legacy: Whether to force the use of the legacy Metadata V14 decoder
692696
693697
Returns:
694698
Decoded object
@@ -697,7 +701,7 @@ def decode_scale(
697701
# Decode AccountId bytes to SS58 address
698702
return ss58_encode(scale_bytes, self.ss58_format)
699703
else:
700-
if self.runtime.metadata_v15 is not None:
704+
if self.runtime.metadata_v15 is not None and force_legacy is False:
701705
obj = decode_by_type_string(
702706
type_string, self.runtime.registry, scale_bytes
703707
)
@@ -1631,16 +1635,15 @@ def convert_event_data(data):
16311635
block_hash = self.get_chain_head()
16321636

16331637
storage_obj = self.query(
1634-
module="System", storage_function="Events", block_hash=block_hash
1638+
module="System",
1639+
storage_function="Events",
1640+
block_hash=block_hash,
1641+
force_legacy_decode=True,
16351642
)
1643+
# bt-decode Metadata V15 is not ideal for events. Force legacy decoding for this
16361644
if storage_obj:
16371645
for item in list(storage_obj):
1638-
try:
1639-
events.append(convert_event_data(item))
1640-
except (
1641-
AttributeError
1642-
): # indicates this was legacy decoded with scalecodec
1643-
events.append(item)
1646+
events.append(item)
16441647
return events
16451648

16461649
def get_metadata(self, block_hash=None) -> MetadataV15:
@@ -1822,6 +1825,7 @@ def _process_response(
18221825
value_scale_type: Optional[str] = None,
18231826
storage_item: Optional[ScaleType] = None,
18241827
result_handler: Optional[ResultHandler] = None,
1828+
force_legacy_decode: bool = False,
18251829
) -> tuple[Any, bool]:
18261830
"""
18271831
Processes the RPC call response by decoding it, returning it as is, or setting a handler for subscriptions,
@@ -1833,6 +1837,7 @@ def _process_response(
18331837
value_scale_type: Scale Type string used for decoding ScaleBytes results
18341838
storage_item: The ScaleType object used for decoding ScaleBytes results
18351839
result_handler: the result handler coroutine used for handling longer-running subscriptions
1840+
force_legacy_decode: Whether to force legacy Metadata V14 decoding of the response
18361841
18371842
Returns:
18381843
(decoded response, completion)
@@ -1854,7 +1859,9 @@ def _process_response(
18541859
q = bytes(query_value)
18551860
else:
18561861
q = query_value
1857-
result = self.decode_scale(value_scale_type, q)
1862+
result = self.decode_scale(
1863+
value_scale_type, q, force_legacy=force_legacy_decode
1864+
)
18581865
if isinstance(result_handler, Callable):
18591866
# For multipart responses as a result of subscriptions.
18601867
message, bool_result = result_handler(result, subscription_id)
@@ -1868,6 +1875,7 @@ def _make_rpc_request(
18681875
storage_item: Optional[ScaleType] = None,
18691876
result_handler: Optional[ResultHandler] = None,
18701877
attempt: int = 1,
1878+
force_legacy_decode: bool = False,
18711879
) -> RequestManager.RequestResults:
18721880
request_manager = RequestManager(payloads)
18731881
_received = {}
@@ -1901,6 +1909,7 @@ def _make_rpc_request(
19011909
storage_item,
19021910
result_handler,
19031911
attempt + 1,
1912+
force_legacy_decode,
19041913
)
19051914
if "id" in response:
19061915
_received[response["id"]] = response
@@ -1932,6 +1941,7 @@ def _make_rpc_request(
19321941
value_scale_type,
19331942
storage_item,
19341943
result_handler,
1944+
force_legacy_decode,
19351945
)
19361946
request_manager.add_response(
19371947
item_id, decoded_response, complete
@@ -2870,6 +2880,7 @@ def query(
28702880
raw_storage_key: Optional[bytes] = None,
28712881
subscription_handler=None,
28722882
reuse_block_hash: bool = False,
2883+
force_legacy_decode: bool = False,
28732884
) -> Optional[Union["ScaleObj", Any]]:
28742885
"""
28752886
Queries substrate. This should only be used when making a single request. For multiple requests,
@@ -2895,6 +2906,7 @@ def query(
28952906
value_scale_type,
28962907
storage_item,
28972908
result_handler=subscription_handler,
2909+
force_legacy_decode=force_legacy_decode,
28982910
)
28992911
result = responses[preprocessed.queryable][0]
29002912
if isinstance(result, (list, tuple, int, float)):

tests/integration_tests/test_async_substrate_interface.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,20 @@ async def test_fully_exhaust_query_map():
115115
fully_exhausted_records_count += 1
116116
assert fully_exhausted_records_count == initial_records_count_fully_exhaust
117117
assert initial_records_count_fully_exhaust == exhausted_records_count
118+
119+
120+
@pytest.mark.asyncio
121+
async def test_get_events_proper_decoding():
122+
# known block/hash pair that has the events we seek to decode
123+
block = 5846788
124+
block_hash = "0x0a1c45063a59b934bfee827caa25385e60d5ec1fd8566a58b5cc4affc4eec412"
125+
126+
async with AsyncSubstrateInterface(ARCHIVE_ENTRYPOINT) as substrate:
127+
all_events = await substrate.get_events(block_hash=block_hash)
128+
event = all_events[1]
129+
print(type(event["attributes"]))
130+
assert event["attributes"] == (
131+
"5G1NjW9YhXLadMWajvTkfcJy6up3yH2q1YzMXDTi6ijanChe",
132+
30,
133+
"0xa6b4e5c8241d60ece0c25056b19f7d21ae845269fc771ad46bf3e011865129a5",
134+
)

tests/integration_tests/test_substrate_interface.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,19 @@ def test_ss58_conversion():
6868
if len(value.value) > 0:
6969
for decoded_key in value.value:
7070
assert isinstance(decoded_key, str)
71+
72+
73+
def test_get_events_proper_decoding():
74+
# known block/hash pair that has the events we seek to decode
75+
block = 5846788
76+
block_hash = "0x0a1c45063a59b934bfee827caa25385e60d5ec1fd8566a58b5cc4affc4eec412"
77+
78+
with SubstrateInterface(ARCHIVE_ENTRYPOINT) as substrate:
79+
all_events = substrate.get_events(block_hash=block_hash)
80+
event = all_events[1]
81+
print(type(event["attributes"]))
82+
assert event["attributes"] == (
83+
"5G1NjW9YhXLadMWajvTkfcJy6up3yH2q1YzMXDTi6ijanChe",
84+
30,
85+
"0xa6b4e5c8241d60ece0c25056b19f7d21ae845269fc771ad46bf3e011865129a5",
86+
)

0 commit comments

Comments
 (0)