Skip to content

Commit 2ac18c1

Browse files
committed
Add iterate map
1 parent 0f5c62e commit 2ac18c1

File tree

3 files changed

+81
-11
lines changed

3 files changed

+81
-11
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.vscode
2+
13
# Byte-compiled / optimized / DLL files
24
__pycache__/
35
*.py[cod]

Diff for: README.md

+21-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# Python Substrate Interface
2-
2+
33
[![Travis CI Build Status](https://api.travis-ci.org/polkascan/py-substrate-interface.svg?branch=master)](https://travis-ci.org/polkascan/py-substrate-interface)
4-
[![Latest Version](https://img.shields.io/pypi/v/substrate-interface.svg)](https://pypi.org/project/substrate-interface/)
4+
[![Latest Version](https://img.shields.io/pypi/v/substrate-interface.svg)](https://pypi.org/project/substrate-interface/)
55
[![Supported Python versions](https://img.shields.io/pypi/pyversions/substrate-interface.svg)](https://pypi.org/project/substrate-interface/)
66
[![License](https://img.shields.io/pypi/l/substrate-interface.svg)](https://github.com/polkascan/py-substrate-interface/blob/master/LICENSE)
77

88
Python Substrate Interface Library
99

1010
## Description
1111
This library specializes in interfacing with a Substrate node, providing additional convenience methods to deal with
12-
SCALE encoding/decoding (the default output and input format of the Substrate JSONRPC), metadata parsing, type registry
12+
SCALE encoding/decoding (the default output and input format of the Substrate JSONRPC), metadata parsing, type registry
1313
management and versioning of types.
1414

1515
## Documentation
@@ -33,11 +33,11 @@ substrate = SubstrateInterface(
3333
type_registry_preset='kusama'
3434
)
3535

36-
substrate.get_chain_head()
36+
substrate.get_chain_head()
3737
```
3838
Note on support for wss, this is still quite limited at the moment as connections are not reused yet. Until support is
3939
improved it is prefered to use http endpoints (e.g. http://127.0.0.1:9933)
40-
40+
4141
### Get extrinsics for a certain block
4242

4343
```python
@@ -74,8 +74,8 @@ for extrinsic in result['block']['extrinsics']:
7474

7575

7676
### Make a storage call
77-
The modules and storage functions are provided in the metadata (see `substrate.get_metadata_storage_functions()`),
78-
parameters will be automatically converted to SCALE-bytes (also including decoding of SS58 addresses).
77+
The modules and storage functions are provided in the metadata (see `substrate.get_metadata_storage_functions()`),
78+
parameters will be automatically converted to SCALE-bytes (also including decoding of SS58 addresses).
7979

8080
```python
8181
balance_info = substrate.get_runtime_state(
@@ -107,6 +107,17 @@ if balance_info:
107107
))
108108
```
109109

110+
Or get all the key pairs of a map:
111+
112+
```python
113+
# Get all the stash and controller bondings.
114+
all_bonded_stash_ctrls = substrate.iterate_map(
115+
module='Staking',
116+
storage_function='Bonded',
117+
block_hash=block_hash
118+
)
119+
```
120+
110121
### Create and send signed extrinsics
111122

112123
The following code snippet illustrates how to create a call, wrap it in an signed extrinsic and send it to the network:
@@ -157,9 +168,9 @@ if keypair.verify("Test123", signature):
157168
### Metadata and type versioning
158169

159170
Py-substrate-interface makes it also possible to easily interprete changed types and historic runtimes. As an example
160-
we create an (not very useful) historic call of a module that has been removed later on: retrieval of historic metadata and
161-
apply the correct version of types in the type registry is all done automatically. Because parsing of metadata and
162-
type registry is quite heavy, the result will be cached per runtime id. In the future there could be support for
171+
we create an (not very useful) historic call of a module that has been removed later on: retrieval of historic metadata and
172+
apply the correct version of types in the type registry is all done automatically. Because parsing of metadata and
173+
type registry is quite heavy, the result will be cached per runtime id. In the future there could be support for
163174
caching backends like Redis to make this cache more persistent.
164175

165176
Create an unsigned extrinsic of a module that was removed by providing block hash:

Diff for: substrateinterface/__init__.py

+58-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,10 @@ async def ws_request(ws_payload):
250250
This method doesn't return but updates the `ws_result` object variable with the result
251251
"""
252252
async with websockets.connect(
253-
self.url
253+
self.url,
254+
max_size=2**32,
255+
read_limit=2**32,
256+
write_limit=2**32,
254257
) as websocket:
255258
await websocket.send(json.dumps(ws_payload))
256259

@@ -729,6 +732,60 @@ def init_runtime(self, block_hash=None, block_id=None):
729732
self.debug_message('Stored metadata for {} in Redis'.format(self.runtime_version))
730733
self.cache_region.set('METADATA_{}'.format(self.runtime_version), self.metadata_decoder)
731734

735+
def iterate_map(self, module, storage_function, block_hash=None):
736+
"""
737+
iterates over all key-pairs localted at the given module and storage_function. The storage
738+
item must be a map.
739+
740+
Parameters
741+
----------
742+
module: The module name in the metadata, e.g. Balances or Account.
743+
storage_function: The storage function name, e.g. FreeBalance or AccountNonce.
744+
block_hash: Optional block hash, when left to None the chain tip will be used.
745+
746+
Returns
747+
-------
748+
A two dimensional list of key-value pairs, both decoded into the given type, e.g.
749+
[[k1, v1], [k2, v2], ...]
750+
"""
751+
self.init_runtime(block_hash=block_hash)
752+
753+
key_type = None
754+
value_type = None
755+
concat_hash_len = None
756+
for metadata_module in self.metadata_decoder.metadata.modules:
757+
if metadata_module.name == module:
758+
if metadata_module.storage:
759+
for storage_item in metadata_module.storage.items:
760+
if storage_item.name == storage_function:
761+
if 'MapType' in storage_item.type:
762+
key_type = storage_item.type['MapType']['key']
763+
value_type = storage_item.type['MapType']['value']
764+
if storage_item.type['MapType']['hasher'] == "Blake2_128Concat":
765+
concat_hash_len = 32
766+
elif storage_item.type['MapType']['hasher'] == "Twox64Concat":
767+
concat_hash_len = 16
768+
else:
769+
raise ValueError('Unsupported hash type')
770+
else:
771+
raise ValueError('Given storage is not a map')
772+
773+
774+
prefix = self.generate_storage_hash(module, storage_function)
775+
prefix_len = len(prefix)
776+
pairs = self.rpc_request(method="state_getPairs", params=[prefix, block_hash]).get('result')
777+
778+
# convert keys to the portion that needs to be decoded.
779+
pairs = map(lambda kp: ["0x" + kp[0][prefix_len + concat_hash_len:], kp[1]], pairs)
780+
781+
# decode both of them
782+
pairs = map(
783+
lambda kp: [self.decode_scale(key_type, kp[0]), self.decode_scale(value_type, kp[1])],
784+
list(pairs)
785+
)
786+
787+
return list(pairs)
788+
732789
def get_runtime_state(self, module, storage_function, params=None, block_hash=None):
733790
"""
734791
Retrieves the storage entry for given module, function and optional parameters at given block hash

0 commit comments

Comments
 (0)