polkascan / py-substrate-interface

Python Substrate Interface
https://polkascan.github.io/py-substrate-interface/
Apache License 2.0
240 stars 114 forks source link

[Question] How to use encoded parameters? #205

Closed BulatSaif closed 2 years ago

BulatSaif commented 2 years ago

Hi, I am migrating a scripts from polkadot-js-api to python library I have command:

polkadot-js-api --ws ws://127.0.0.1:9944 --seed <seed> tx.session.setKeys <session_key: 0x...> None

It also works in polkadot.js.org: image

But same steps in python does not work: Error:

ValueError: Element "grandpa" of struct is missing in given value

Script:

substrate_client = SubstrateInterface(
    url="ws://127.0.0.1:9944"
)

rotate_keys = substrate_client.rpc_request(method="author_rotateKeys", params=[])['result']
print(rotate_keys)
# 0x55cd921526c42e90e26d2680a4bf0968b393cb4847...
keypair = Keypair.create_from_uri("bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice")
call = substrate_client.compose_call(  # <- scrip fails here
    call_module='Session',
    call_function='set_keys',
    call_params={
        'keys': rotate_keys,
        'proof': 'None'
    }
)
extrinsic = substrate_client.create_signed_extrinsic(
    call=call,
    keypair=keypair,
)

try:
    receipt = substrate_client.submit_extrinsic(extrinsic, wait_for_inclusion=True)
    print("Extrinsic '{}' sent".format(receipt.extrinsic_hash))
except Exception as e:
    print("Failed to send call: {}, Error: {}".format(call, e))

As I understand library tries to encode already encoded parameter , so if I replace hex string with map of keys all works.

call_params={
  'keys': {'grandpa':'0x7e4230880436450bc1cc3d65039d83a6a728bd5f7d0364499afaab7a16e79c3d',
            'babe':'0x50105b266cd9e427a634d332c26b7d37df8844570a2b5e867d54fa0b1da7771e',
            'im_online':'0x723fd195e5a8b60f3f637f5a65840e09dd3e1a2ec1bd2495278783e673b68c29',
            'para_validator':'0x323710e92ac2b8068445b6a3bf75fee366e12370a7e365b492dd719b7057954c',
            'para_assignment':'0xa8123ed90963ba5f698a3ac230d70d50393dfc9a29d6d07175ed382aaf60fc54',
            'authority_discovery':'0x9041e95e6705e800db99caaf787acaadfa7879f9fb979ce44a778e50cb79eb47',
            'beefy':'0x035e4cb4dcda61d44cd6107e966e680723f7d5ac6cf8762cff7a35da21a32eb94e'
            },

Questions

  1. Is it possible to use encoded value as parameter?
  2. Can you please provide example how to decode/encode results from rpc call?
arjanz commented 2 years ago

1.

ValueError: Element "grandpa" of struct is missing in given value

What I noticed here is there is a duplicate nested key 'keys', the following call worked for me:

call = substrate_client.compose_call(  
    call_module='Session',
    call_function='set_keys',
    call_params={
        'keys': {
            'grandpa':'0x7e4230880436450bc1cc3d65039d83a6a728bd5f7d0364499afaab7a16e79c3d',
            'babe':'0x50105b266cd9e427a634d332c26b7d37df8844570a2b5e867d54fa0b1da7771e',
            'im_online':'0x723fd195e5a8b60f3f637f5a65840e09dd3e1a2ec1bd2495278783e673b68c29',
            'para_validator':'0x323710e92ac2b8068445b6a3bf75fee366e12370a7e365b492dd719b7057954c',
            'para_assignment':'0xa8123ed90963ba5f698a3ac230d70d50393dfc9a29d6d07175ed382aaf60fc54',
            'authority_discovery':'0x9041e95e6705e800db99caaf787acaadfa7879f9fb979ce44a778e50cb79eb47',
            'beefy':'0x035e4cb4dcda61d44cd6107e966e680723f7d5ac6cf8762cff7a35da21a32eb94e'
        },
        'proof': 'None'
    }
)

2.

Storage calls are automatically decoded using the function query(), other RPC calls have to be decoded manually. What you can do is:

rotate_keys = substrate_client.rpc_request(method="author_rotateKeys", params=[])['result']
rotate_keys_decoded = substrate_client.decode_scale("polkadot_runtime::SessionKeys", rotate_keys)

Caveat here is the path of the SessionKeys type include the name of the runtime, so for Kusama for example it would polkadot_runtime::SessionKeys.

BulatSaif commented 2 years ago

Thank you for fast replay!

Is there a way to get the type dynamically? I am testing in rococo and "polkadot_runtime::SessionKeys" did not work.

NotImplementedError: Decoder class for "polkadot_runtime::SessionKeys" not found
arjanz commented 2 years ago

Is there a way to get the type dynamically? I am testing in rococo and "polkadot_runtime::SessionKeys" did not work.

Yes unfortunately the runtime name is in the path of some types, for Rococo the type string would be rococo_runtime::SessionKeys.

Maybe I can figure out if in some cases I can make this more generic, or make types available for their last part SessionKeys (if unique) as well.

BulatSaif commented 2 years ago

@arjanz, Thank you for helping!

In case someone needs my workaround:

rotate_keys = substrate_client.rpc_request(method="author_rotateKeys", params=[])['result']

# Get type name (rococo_runtime::SessionKeys) from runtime by id
type_id = substrate_client.get_metadata_call_function('Session', 'set_keys')['fields'][0]['type']
print(type_id)  # 199
metadata = substrate_client.get_runtime_metadata()['result'][1]
runtime_version = list(metadata.keys())[0]
types = metadata[runtime_version]['types']['types']
type_dict = next(item for item in types if item["id"] == type_id)
print(type_dict)  # {'id': 199, 'type': {'path': ['rococo_runtime', 'SessionKeys'], 'params': [], 'def': {'composite': {'fields': [{'name': 'grandpa', 'type': 34,...
type_string = "::".join(type_dict['type']['path'])

# decode rotate_keys
print(substrate_client.decode_scale(type_string, rotate_keys))
# {'grandpa': '0x760cc41f74fd549e8bba788c7202a14eb61e8087b4405e2e1bbd5a68c6fa417a', 'babe': '0x12fc0...

I believe mapping between RPC metod and value type returned should exist somewhere, because in some cases RPC meted returns JSON (example: chain_getHeader), in another it returns hex (Example: author_rotateKeys). But I was not able to find such information.

arjanz commented 2 years ago

Wanted to add that if you know the type_id, you can also easily reconstruct the type_string with scale_info::<type_id>, in this case scale_info::199

BulatSaif commented 2 years ago

Wanted to add that if you know the type_id, you can also easily reconstruct the type_string with scale_info::, in this case scale_info::199

That's what I was looking for, Thank you!