22from abc import ABC
33from collections import defaultdict , deque
44from collections .abc import Iterable
5+ from contextlib import suppress
56from dataclasses import dataclass
67from datetime import datetime
78from typing import Optional , Union , Any
1617
1718from .const import SS58_FORMAT
1819from .utils import json
20+ from .utils .cache import AsyncSqliteDB
1921
2022logger = logging .getLogger ("async_substrate_interface" )
2123
@@ -34,8 +36,8 @@ class RuntimeCache:
3436 is important you are utilizing the correct version.
3537 """
3638
37- blocks : dict [int , "Runtime" ]
38- block_hashes : dict [str , "Runtime" ]
39+ blocks : dict [int , str ]
40+ block_hashes : dict [str , int ]
3941 versions : dict [int , "Runtime" ]
4042 last_used : Optional ["Runtime" ]
4143
@@ -56,10 +58,10 @@ def add_item(
5658 Adds a Runtime object to the cache mapped to its version, block number, and/or block hash.
5759 """
5860 self .last_used = runtime
59- if block is not None :
60- self .blocks [block ] = runtime
61- if block_hash is not None :
62- self .block_hashes [block_hash ] = runtime
61+ if block is not None and block_hash is not None :
62+ self .blocks [block ] = block_hash
63+ if block_hash is not None and runtime_version is not None :
64+ self .block_hashes [block_hash ] = runtime_version
6365 if runtime_version is not None :
6466 self .versions [runtime_version ] = runtime
6567
@@ -73,33 +75,52 @@ def retrieve(
7375 Retrieves a Runtime object from the cache, using the key of its block number, block hash, or runtime version.
7476 Retrieval happens in this order. If no Runtime is found mapped to any of your supplied keys, returns `None`.
7577 """
78+ runtime = None
7679 if block is not None :
77- runtime = self .blocks .get (block )
78- if runtime is not None :
79- if block_hash is not None :
80- # if lookup occurs for block_hash and block, but only block matches, also map to block_hash
81- self .add_item (runtime , block_hash = block_hash )
80+ if block_hash is not None :
81+ self .blocks [block ] = block_hash
82+ if runtime_version is not None :
83+ self .block_hashes [block_hash ] = runtime_version
84+ with suppress (KeyError ):
85+ runtime = self .versions [self .block_hashes [self .blocks [block ]]]
8286 self .last_used = runtime
8387 return runtime
8488 if block_hash is not None :
85- runtime = self .block_hashes .get (block_hash )
86- if runtime is not None :
87- if block is not None :
88- # if lookup occurs for block_hash and block, but only block_hash matches, also map to block
89- self .add_item (runtime , block = block )
89+ if runtime_version is not None :
90+ self .block_hashes [block_hash ] = runtime_version
91+ with suppress (KeyError ):
92+ runtime = self .versions [self .block_hashes [block_hash ]]
9093 self .last_used = runtime
9194 return runtime
9295 if runtime_version is not None :
93- runtime = self .versions .get (runtime_version )
94- if runtime is not None :
95- # if runtime_version matches, also map to block and block_hash (if supplied)
96- if block is not None :
97- self .add_item (runtime , block = block )
98- if block_hash is not None :
99- self .add_item (runtime , block_hash = block_hash )
96+ with suppress (KeyError ):
97+ runtime = self .versions [runtime_version ]
10098 self .last_used = runtime
10199 return runtime
102- return None
100+ return runtime
101+
102+ async def load_from_disk (self , chain_endpoint : str ):
103+ db = AsyncSqliteDB (chain_endpoint = chain_endpoint )
104+ (
105+ block_mapping ,
106+ block_hash_mapping ,
107+ runtime_version_mapping ,
108+ ) = await db .load_runtime_cache (chain_endpoint )
109+ if not any ([block_mapping , block_hash_mapping , runtime_version_mapping ]):
110+ logger .debug ("No runtime mappings in disk cache" )
111+ else :
112+ logger .debug ("Found runtime mappings in disk cache" )
113+ self .blocks = block_mapping
114+ self .block_hashes = block_hash_mapping
115+ self .versions = {
116+ x : Runtime .deserialize (y ) for x , y in runtime_version_mapping .items ()
117+ }
118+
119+ async def dump_to_disk (self , chain_endpoint : str ):
120+ db = AsyncSqliteDB (chain_endpoint = chain_endpoint )
121+ await db .dump_runtime_cache (
122+ chain_endpoint , self .blocks , self .block_hashes , self .versions
123+ )
103124
104125
105126class Runtime :
@@ -149,6 +170,45 @@ def __init__(
149170 if registry is not None :
150171 self .load_registry_type_map ()
151172
173+ def serialize (self ):
174+ metadata_value = self .metadata .data .data
175+ return {
176+ "chain" : self .chain ,
177+ "type_registry" : self .type_registry ,
178+ "metadata_value" : metadata_value ,
179+ "metadata_v15" : self .metadata_v15 .encode_to_metadata_option (),
180+ "runtime_info" : {
181+ "specVersion" : self .runtime_version ,
182+ "transactionVersion" : self .transaction_version ,
183+ },
184+ "registry" : self .registry .registry if self .registry is not None else None ,
185+ "ss58_format" : self .ss58_format ,
186+ }
187+
188+ @classmethod
189+ def deserialize (cls , serialized : dict ) -> "Runtime" :
190+ ss58_format = serialized ["ss58_format" ]
191+ runtime_config = RuntimeConfigurationObject (ss58_format = ss58_format )
192+ runtime_config .clear_type_registry ()
193+ runtime_config .update_type_registry (load_type_registry_preset (name = "core" ))
194+ metadata = runtime_config .create_scale_object (
195+ "MetadataVersioned" , data = ScaleBytes (serialized ["metadata_value" ])
196+ )
197+ metadata .decode ()
198+ registry = PortableRegistry .from_json (serialized ["registry" ])
199+ return cls (
200+ chain = serialized ["chain" ],
201+ metadata = metadata ,
202+ type_registry = serialized ["type_registry" ],
203+ runtime_config = runtime_config ,
204+ metadata_v15 = MetadataV15 .decode_from_metadata_option (
205+ serialized ["metadata_v15" ]
206+ ),
207+ registry = registry ,
208+ ss58_format = ss58_format ,
209+ runtime_info = serialized ["runtime_info" ],
210+ )
211+
152212 def load_runtime (self ):
153213 """
154214 Initial loading of the runtime's type registry information.
0 commit comments