diff --git a/substrateinterface/base.py b/substrateinterface/base.py index 0d02e82..de0625f 100644 --- a/substrateinterface/base.py +++ b/substrateinterface/base.py @@ -226,7 +226,10 @@ def supports_rpc_method(self, name: str) -> bool: bool """ if self.config.get('rpc_methods') is None: - self.config['rpc_methods'] = self.rpc_request("rpc_methods", [])['result']['methods'] + self.config['rpc_methods'] = [] + result = self.rpc_request("rpc_methods", []).get('result') + if result: + self.config['rpc_methods'] = result.get('methods', []) return name in self.config['rpc_methods'] @@ -428,7 +431,10 @@ def get_chain_head(self): ------- """ - response = self.rpc_request("chain_getHead", []) + if self.supports_rpc_method("chain_getHead"): + response = self.rpc_request("chain_getHead", []) + else: + response = self.rpc_request("chain_getBlockHash", []) if response is not None: if 'error' in response: @@ -438,13 +444,13 @@ def get_chain_head(self): def get_chain_finalised_head(self): """ - A pass-though to existing JSONRPC method `chain_getFinalisedHead` + A pass-though to existing JSONRPC method `chain_getFinalizedHead` Returns ------- """ - response = self.rpc_request("chain_getFinalisedHead", []) + response = self.rpc_request("chain_getFinalizedHead", []) if response is not None: if 'error' in response: @@ -452,7 +458,7 @@ def get_chain_finalised_head(self): return response.get('result') - def get_block_hash(self, block_id: int) -> str: + def get_block_hash(self, block_id: int = None) -> str: """ A pass-though to existing JSONRPC method `chain_getBlockHash` @@ -526,7 +532,7 @@ def get_block_metadata(self, block_hash=None, decode=True): def get_storage_by_key(self, block_hash: str, storage_key: str): """ - A pass-though to existing JSONRPC method `state_getStorageAt` + A pass-though to existing JSONRPC method `state_getStorage` Parameters ---------- @@ -538,7 +544,11 @@ def get_storage_by_key(self, block_hash: str, storage_key: str): """ - response = self.rpc_request("state_getStorageAt", [storage_key, block_hash]) + if self.supports_rpc_method('state_getStorageAt'): + response = self.rpc_request("state_getStorageAt", [storage_key, block_hash]) + else: + response = self.rpc_request("state_getStorage", [storage_key, block_hash]) + if 'result' in response: return response.get('result') elif 'error' in response: @@ -557,7 +567,10 @@ def get_block_runtime_version(self, block_hash): ------- """ - response = self.rpc_request("chain_getRuntimeVersion", [block_hash]) + if self.supports_rpc_method("state_getRuntimeVersion"): + response = self.rpc_request("state_getRuntimeVersion", [block_hash]) + else: + response = self.rpc_request("chain_getRuntimeVersion", [block_hash]) if 'error' in response: raise SubstrateRequestException(response['error']['message']) @@ -948,7 +961,10 @@ def result_handler(storage_key, updated_obj, update_nr, subscription_id): else: - response = self.rpc_request("state_getStorageAt", [storage_key.to_hex(), block_hash]) + if self.supports_rpc_method('state_getStorageAt'): + response = self.rpc_request("state_getStorageAt", [storage_key.to_hex(), block_hash]) + else: + response = self.rpc_request("state_getStorage", [storage_key.to_hex(), block_hash]) if 'error' in response: raise SubstrateRequestException(response['error']['message']) @@ -1667,17 +1683,21 @@ def submit_extrinsic(self, extrinsic: GenericExtrinsic, wait_for_inclusion: bool def result_handler(message, update_nr, subscription_id): # Check if extrinsic is included and finalized if 'params' in message and type(message['params']['result']) is dict: - if 'finalized' in message['params']['result'] and wait_for_finalization: + + # Convert result enum to lower for backwards compatibility + message_result = {k.lower(): v for k, v in message['params']['result'].items()} + + if 'finalized' in message_result and wait_for_finalization: self.rpc_request('author_unwatchExtrinsic', [subscription_id]) return { - 'block_hash': message['params']['result']['finalized'], + 'block_hash': message_result['finalized'], 'extrinsic_hash': '0x{}'.format(extrinsic.extrinsic_hash.hex()), 'finalized': True } - elif 'inBlock' in message['params']['result'] and wait_for_inclusion and not wait_for_finalization: + elif 'inblock' in message_result and wait_for_inclusion and not wait_for_finalization: self.rpc_request('author_unwatchExtrinsic', [subscription_id]) return { - 'block_hash': message['params']['result']['inBlock'], + 'block_hash': message_result['inblock'], 'extrinsic_hash': '0x{}'.format(extrinsic.extrinsic_hash.hex()), 'finalized': False } @@ -2211,7 +2231,9 @@ def decode_block(block_data, block_data_hash=None): if block_data_hash: block_data['header']['hash'] = block_data_hash - block_data['header']['number'] = int(block_data['header']['number'], 16) + if type(block_data['header']['number']) is str: + # Convert block number from hex (backwards compatibility) + block_data['header']['number'] = int(block_data['header']['number'], 16) extrinsic_cls = self.runtime_config.get_decoder_class('Extrinsic') @@ -2232,72 +2254,73 @@ def decode_block(block_data, block_data_hash=None): block_data['extrinsics'][idx] = None for idx, log_data in enumerate(block_data['header']["digest"]["logs"]): + if type(log_data) is str: + # Convert digest log from hex (backwards compatibility) + try: + log_digest_cls = self.runtime_config.get_decoder_class('sp_runtime::generic::digest::DigestItem') - try: - log_digest_cls = self.runtime_config.get_decoder_class('sp_runtime::generic::digest::DigestItem') - - if log_digest_cls is None: - raise NotImplementedError("No decoding class found for 'DigestItem'") + if log_digest_cls is None: + raise NotImplementedError("No decoding class found for 'DigestItem'") - log_digest = log_digest_cls(data=ScaleBytes(log_data)) - log_digest.decode() + log_digest = log_digest_cls(data=ScaleBytes(log_data)) + log_digest.decode() - block_data['header']["digest"]["logs"][idx] = log_digest + block_data['header']["digest"]["logs"][idx] = log_digest - if include_author and 'PreRuntime' in log_digest.value: + if include_author and 'PreRuntime' in log_digest.value: - if self.implements_scaleinfo(): + if self.implements_scaleinfo(): - engine = bytes(log_digest[1][0]) - # Retrieve validator set - validator_set = self.query("Session", "Validators", block_hash=block_hash) + engine = bytes(log_digest[1][0]) + # Retrieve validator set + validator_set = self.query("Session", "Validators", block_hash=block_hash) - if engine == b'BABE': - babe_predigest = self.runtime_config.create_scale_object( - type_string='RawBabePreDigest', - data=ScaleBytes(bytes(log_digest[1][1])) - ) + if engine == b'BABE': + babe_predigest = self.runtime_config.create_scale_object( + type_string='RawBabePreDigest', + data=ScaleBytes(bytes(log_digest[1][1])) + ) - babe_predigest.decode() + babe_predigest.decode() - rank_validator = babe_predigest[1].value['authority_index'] + rank_validator = babe_predigest[1].value['authority_index'] - block_author = validator_set[rank_validator] - block_data['author'] = block_author.value + block_author = validator_set[rank_validator] + block_data['author'] = block_author.value - elif engine == b'aura': - aura_predigest = self.runtime_config.create_scale_object( - type_string='RawAuraPreDigest', - data=ScaleBytes(bytes(log_digest[1][1])) - ) + elif engine == b'aura': + aura_predigest = self.runtime_config.create_scale_object( + type_string='RawAuraPreDigest', + data=ScaleBytes(bytes(log_digest[1][1])) + ) - aura_predigest.decode() + aura_predigest.decode() - rank_validator = aura_predigest.value['slot_number'] % len(validator_set) + rank_validator = aura_predigest.value['slot_number'] % len(validator_set) - block_author = validator_set[rank_validator] - block_data['author'] = block_author.value + block_author = validator_set[rank_validator] + block_data['author'] = block_author.value + else: + raise NotImplementedError( + f"Cannot extract author for engine {log_digest.value['PreRuntime'][0]}" + ) else: - raise NotImplementedError( - f"Cannot extract author for engine {log_digest.value['PreRuntime'][0]}" - ) - else: - if log_digest.value['PreRuntime']['engine'] == 'BABE': - validator_set = self.query("Session", "Validators", block_hash=block_hash) - rank_validator = log_digest.value['PreRuntime']['data']['authority_index'] + if log_digest.value['PreRuntime']['engine'] == 'BABE': + validator_set = self.query("Session", "Validators", block_hash=block_hash) + rank_validator = log_digest.value['PreRuntime']['data']['authority_index'] - block_author = validator_set.elements[rank_validator] - block_data['author'] = block_author.value - else: - raise NotImplementedError( - f"Cannot extract author for engine {log_digest.value['PreRuntime']['engine']}" - ) + block_author = validator_set.elements[rank_validator] + block_data['author'] = block_author.value + else: + raise NotImplementedError( + f"Cannot extract author for engine {log_digest.value['PreRuntime']['engine']}" + ) - except Exception: - if not ignore_decoding_errors: - raise - block_data['header']["digest"]["logs"][idx] = None + except Exception: + if not ignore_decoding_errors: + raise + block_data['header']["digest"]["logs"][idx] = None return block_data diff --git a/test/test_block.py b/test/test_block.py index 38c1a9a..7271c6f 100644 --- a/test/test_block.py +++ b/test/test_block.py @@ -53,13 +53,13 @@ def mocked_query(module, storage_function, block_hash): def mocked_request(method, params, result_handler=None): - if method in ['chain_getBlockHash', 'chain_getHead', 'chain_getFinalisedHead']: + if method in ['chain_getBlockHash', 'chain_getHead', 'chain_getFinalisedHead', 'chain_getFinalizedHead']: return { "jsonrpc": "2.0", "result": "0xec828914eca09331dad704404479e2899a971a9b5948345dc40abca4ac818f93", "id": 1 } - elif method == 'chain_getRuntimeVersion': + elif method in ['chain_getRuntimeVersion', 'state_getRuntimeVersion']: return { "jsonrpc": "2.0", "result": {"specVersion": 100, "transactionVersion": 1}, @@ -156,6 +156,12 @@ def mocked_request(method, params, result_handler=None): "jsonrpc": "2.0", "result": True } + elif method == 'rpc_methods': + return { + "jsonrpc": "2.0", + "result": {'methods': ['account_nextIndex', 'author_hasKey', 'author_hasSessionKeys', 'author_insertKey', 'author_pendingExtrinsics', 'author_removeExtrinsic', 'author_rotateKeys', 'author_submitAndWatchExtrinsic', 'author_submitExtrinsic', 'author_unwatchExtrinsic', 'babe_epochAuthorship', 'chainHead_unstable_body', 'chainHead_unstable_call', 'chainHead_unstable_follow', 'chainHead_unstable_genesisHash', 'chainHead_unstable_header', 'chainHead_unstable_stopBody', 'chainHead_unstable_stopCall', 'chainHead_unstable_stopStorage', 'chainHead_unstable_storage', 'chainHead_unstable_unfollow', 'chainHead_unstable_unpin', 'chainSpec_unstable_chainName', 'chainSpec_unstable_genesisHash', 'chainSpec_unstable_properties', 'chain_getBlock', 'chain_getBlockHash', 'chain_getFinalisedHead', 'chain_getFinalizedHead', 'chain_getHead', 'chain_getHeader', 'chain_getRuntimeVersion', 'chain_subscribeAllHeads', 'chain_subscribeFinalisedHeads', 'chain_subscribeFinalizedHeads', 'chain_subscribeNewHead', 'chain_subscribeNewHeads', 'chain_subscribeRuntimeVersion', 'chain_unsubscribeAllHeads', 'chain_unsubscribeFinalisedHeads', 'chain_unsubscribeFinalizedHeads', 'chain_unsubscribeNewHead', 'chain_unsubscribeNewHeads', 'chain_unsubscribeRuntimeVersion', 'childstate_getKeys', 'childstate_getKeysPaged', 'childstate_getKeysPagedAt', 'childstate_getStorage', 'childstate_getStorageEntries', 'childstate_getStorageHash', 'childstate_getStorageSize', 'dev_getBlockStats', 'grandpa_proveFinality', 'grandpa_roundState', 'grandpa_subscribeJustifications', 'grandpa_unsubscribeJustifications', 'mmr_generateProof', 'mmr_root', 'mmr_verifyProof', 'mmr_verifyProofStateless', 'offchain_localStorageGet', 'offchain_localStorageSet', 'payment_queryFeeDetails', 'payment_queryInfo', 'state_call', 'state_callAt', 'state_getChildReadProof', 'state_getKeys', 'state_getKeysPaged', 'state_getKeysPagedAt', 'state_getMetadata', 'state_getPairs', 'state_getReadProof', 'state_getRuntimeVersion', 'state_getStorage', 'state_getStorageAt', 'state_getStorageHash', 'state_getStorageHashAt', 'state_getStorageSize', 'state_getStorageSizeAt', 'state_queryStorage', 'state_queryStorageAt', 'state_subscribeRuntimeVersion', 'state_subscribeStorage', 'state_traceBlock', 'state_trieMigrationStatus', 'state_unsubscribeRuntimeVersion', 'state_unsubscribeStorage', 'subscribe_newHead', 'sync_state_genSyncSpec', 'system_accountNextIndex', 'system_addLogFilter', 'system_addReservedPeer', 'system_chain', 'system_chainType', 'system_dryRun', 'system_dryRunAt', 'system_health', 'system_localListenAddresses', 'system_localPeerId', 'system_name', 'system_nodeRoles', 'system_peers', 'system_properties', 'system_removeReservedPeer', 'system_reservedPeers', 'system_resetLogFilter', 'system_syncState', 'system_unstable_networkState', 'system_version', 'transaction_unstable_submitAndWatch', 'transaction_unstable_unwatch', 'unsubscribe_newHead']}, + "id": 1 + } raise ValueError(f"Unsupported mocked method {method}") diff --git a/test/test_helper_functions.py b/test/test_helper_functions.py index e59f1dc..368ca7f 100644 --- a/test/test_helper_functions.py +++ b/test/test_helper_functions.py @@ -44,7 +44,7 @@ def setUpClass(cls): cls.substrate.get_block_metadata = MagicMock(return_value=metadata_decoder) def mocked_request(method, params): - if method == 'chain_getRuntimeVersion': + if method == 'state_getRuntimeVersion': return { "jsonrpc": "2.0", "result": {"specVersion": 2023}, @@ -71,6 +71,12 @@ def mocked_request(method, params): "result": "0xe1781813275653a970b4260298b3858b36d38e072256dad674f7c786a0cae236", "id": 1 } + elif method == 'rpc_methods': + return { + "jsonrpc": "2.0", + "result": {'methods': ['account_nextIndex', 'author_hasKey', 'author_hasSessionKeys', 'author_insertKey', 'author_pendingExtrinsics', 'author_removeExtrinsic', 'author_rotateKeys', 'author_submitAndWatchExtrinsic', 'author_submitExtrinsic', 'author_unwatchExtrinsic', 'babe_epochAuthorship', 'chainHead_unstable_body', 'chainHead_unstable_call', 'chainHead_unstable_follow', 'chainHead_unstable_genesisHash', 'chainHead_unstable_header', 'chainHead_unstable_stopBody', 'chainHead_unstable_stopCall', 'chainHead_unstable_stopStorage', 'chainHead_unstable_storage', 'chainHead_unstable_unfollow', 'chainHead_unstable_unpin', 'chainSpec_unstable_chainName', 'chainSpec_unstable_genesisHash', 'chainSpec_unstable_properties', 'chain_getBlock', 'chain_getBlockHash', 'chain_getFinalisedHead', 'chain_getFinalizedHead', 'chain_getHead', 'chain_getHeader', 'chain_getRuntimeVersion', 'chain_subscribeAllHeads', 'chain_subscribeFinalisedHeads', 'chain_subscribeFinalizedHeads', 'chain_subscribeNewHead', 'chain_subscribeNewHeads', 'chain_subscribeRuntimeVersion', 'chain_unsubscribeAllHeads', 'chain_unsubscribeFinalisedHeads', 'chain_unsubscribeFinalizedHeads', 'chain_unsubscribeNewHead', 'chain_unsubscribeNewHeads', 'chain_unsubscribeRuntimeVersion', 'childstate_getKeys', 'childstate_getKeysPaged', 'childstate_getKeysPagedAt', 'childstate_getStorage', 'childstate_getStorageEntries', 'childstate_getStorageHash', 'childstate_getStorageSize', 'dev_getBlockStats', 'grandpa_proveFinality', 'grandpa_roundState', 'grandpa_subscribeJustifications', 'grandpa_unsubscribeJustifications', 'mmr_generateProof', 'mmr_root', 'mmr_verifyProof', 'mmr_verifyProofStateless', 'offchain_localStorageGet', 'offchain_localStorageSet', 'payment_queryFeeDetails', 'payment_queryInfo', 'state_call', 'state_callAt', 'state_getChildReadProof', 'state_getKeys', 'state_getKeysPaged', 'state_getKeysPagedAt', 'state_getMetadata', 'state_getPairs', 'state_getReadProof', 'state_getRuntimeVersion', 'state_getStorage', 'state_getStorageAt', 'state_getStorageHash', 'state_getStorageHashAt', 'state_getStorageSize', 'state_getStorageSizeAt', 'state_queryStorage', 'state_queryStorageAt', 'state_subscribeRuntimeVersion', 'state_subscribeStorage', 'state_traceBlock', 'state_trieMigrationStatus', 'state_unsubscribeRuntimeVersion', 'state_unsubscribeStorage', 'subscribe_newHead', 'sync_state_genSyncSpec', 'system_accountNextIndex', 'system_addLogFilter', 'system_addReservedPeer', 'system_chain', 'system_chainType', 'system_dryRun', 'system_dryRunAt', 'system_health', 'system_localListenAddresses', 'system_localPeerId', 'system_name', 'system_nodeRoles', 'system_peers', 'system_properties', 'system_removeReservedPeer', 'system_reservedPeers', 'system_resetLogFilter', 'system_syncState', 'system_unstable_networkState', 'system_version', 'transaction_unstable_submitAndWatch', 'transaction_unstable_unwatch', 'unsubscribe_newHead']}, + "id": 1 + } raise NotImplementedError(method) diff --git a/test/test_rpc_compatibility.py b/test/test_rpc_compatibility.py new file mode 100644 index 0000000..12138de --- /dev/null +++ b/test/test_rpc_compatibility.py @@ -0,0 +1,180 @@ +# Python Substrate Interface Library +# +# Copyright 2018-2023 Stichting Polkascan (Polkascan Foundation). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import unittest +from unittest.mock import MagicMock + +from scalecodec.type_registry import load_type_registry_file +from test import settings + +from scalecodec.exceptions import RemainingScaleBytesNotEmptyException + +from substrateinterface import SubstrateInterface + +from scalecodec.base import ScaleBytes +from scalecodec.types import Vec, GenericAddress + + +class RPCCompatilibityTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.metadata_fixture_dict = load_type_registry_file( + os.path.join(os.path.dirname(__file__), 'fixtures', 'metadata_hex.json') + ) + + cls.substrate = SubstrateInterface(url='dummy', ss58_format=42, type_registry_preset='substrate-node-template') + metadata_decoder = cls.substrate.runtime_config.create_scale_object( + 'MetadataVersioned', ScaleBytes(cls.metadata_fixture_dict['V14']) + ) + metadata_decoder.decode() + cls.substrate.get_block_metadata = MagicMock(return_value=metadata_decoder) + + def mocked_query(module, storage_function, block_hash): + if module == 'Session' and storage_function == 'Validators': + if block_hash == '0xec828914eca09331dad704404479e2899a971a9b5948345dc40abca4ac818f93': + vec = Vec() + author = GenericAddress() + author.value = '5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY' + vec.elements = [author] + return vec + + raise ValueError(f"Unsupported mocked query {module}.{storage_function} @ {block_hash}") + + def mocked_request(method, params, result_handler=None): + + if method in ['chain_getBlockHash', 'chain_getHead', 'chain_getFinalisedHead', 'chain_getFinalizedHead']: + return { + "jsonrpc": "2.0", + "result": "0xec828914eca09331dad704404479e2899a971a9b5948345dc40abca4ac818f93", + "id": 1 + } + elif method in ['chain_getRuntimeVersion', 'state_getRuntimeVersion']: + return { + "jsonrpc": "2.0", + "result": {"specVersion": 100, "transactionVersion": 1}, + "id": 1 + } + elif method == 'chain_getHeader': + return { + "jsonrpc": "2.0", + "result": { + "digest": { + "logs": ['0x066175726120afe0021000000000', '0x05617572610101567be3d55b4885ce3ac6a7b46b28adf138299acc3eb5f11ffa15c3ed0551f22b7220ec676ea947cd6c8daa6fcfa351b11e62651e6e06f5dde59bb566d36e6989'] + }, + "extrinsicsRoot": "0xeaa9cd48b36a88ba7cf934cdbcd8f2afc0843978912452529ace7ef2da09691d", + "number": 3158840, + "parentHash": "0xf33015565b9978d146cdf648c498649b04c323cd35d9f55fad7d8586d4b42ea2", + "stateRoot": "0xa8b0c74dbf09ee9ff5443076f8298027e3a6505ab6e3f6a683a7d4d137130683" + }, + "id": 1 + } + elif method == 'chain_getBlock': + # Correct + if params[0] == '0xec828914eca09331dad704404479e2899a971a9b5948345dc40abca4ac818f93': + return { + "jsonrpc": "2.0", + "result": { + "block": { + "extrinsics": ['0x280403000b695f47ff8601'], + "header": { + "digest": {'logs': [{'preRuntime': ['0x6e6d6273', '0x6a3f02ac48ee3080aa304fe6c336d6c75b302e08041a5c1d6a9a30541f51b618']}, {'preRuntime': ['0x72616e64', '0xe468581da52a51330797c9f5762d98a241f28b3bde9017307e15cfc9a6ebb6741e7da1282681401b26752a04073ea98335005699817e546db343b457fcc7150cdf9edabb3a7b8e35acd6e763235c9ce415bdb0a22d98024cb6410beeb74d3903']}, {'consensus': ['0x66726f6e', '0x019ad160fc5080a49795f34607dadc267a0a0485dc0e6fa46c0ded42ac102a92813090c50b4ff02aed1ca173df1f8915d9872e47a6c3c71a0a32d36d342d6c00ab9f50a496f171bad265ab5a3512568e1234cdab03f7ee1a98ce6e489d5726deed3c125f5c342d0662ccfcea93d2c34c35f328b183af68502ea9e2a72e83522dd7f884ac7a067b4e5cfae4766fd0b2159809cbe3d1af7eb1747c1a3e675982bd9312b90c5020048fef1349759f22b20a919af96ae7270dd40614eb7345ce35beff07980cb675de6be9bafd8fd2e2322cd2fce6f24d0a23c8a68adcced46da0be3b280ead3a25348bb9e6f7a90aac0a2d8a3d8badcb94c3929a5fd898146ecfd361b39de60b7a194a6e4751aa8b54c435a7a9a6af274700b870528ff0780f6ca63ba7449d5534fb786e33900b30966970f29db6310244860409dd6af96a09f9b82946d3f47a61c3bfb21a43e257b3ddf56d07ce8bfdbcb2c277eca96ebe95a8042ec0faf5d65a20505c1562aba4bde857552766320c61fd748c260a0c334a4bfec1ffedbb288cc8bd3348e4c48bd67118ce3936e3fc67b90aeebab97dbda0f6cfeb7e']}, {'seal': ['0x6e6d6273', '0x561e0be2c6f495f326fbc1bf67943f5ff087133d42860fb1dbe8f260deb4570110e1aab58f6f4064ec1839b351987489bf027e274ae593b52c88d99ae6b92f8e']}]}, + "extrinsicsRoot": "0xeaa9cd48b36a88ba7cf934cdbcd8f2afc0843978912452529ace7ef2da09691d", + "number": "0x67", + "parentHash": "0xf33015565b9978d146cdf648c498649b04c323cd35d9f55fad7d8586d4b42ea2", + "stateRoot": "0xa8b0c74dbf09ee9ff5443076f8298027e3a6505ab6e3f6a683a7d4d137130683" + } + }, + "justification": None + }, + "id": 1 + } + # Raises decoding errors + elif params[0] == '0x40b98c29466fa76eeee21008b50d5cb5d7220712ead554eb97a5fd6ba4bc31b5': + return { + "jsonrpc": "2.0", + "result": { + "block": { + "extrinsics": [ + "0x240402000b9405724377", + "0x280402100b940572437701", + "0x280402000b940572437701", + "0x280402000c940572437701", + ], + "header": { + "digest": { + "logs": [ + "0x066175726120afe0021000000000", + "0x05617572610101567be3d55b4885ce3ac6a7b46b28adf138299acc3eb5f11ffa15c3ed0551f22b7220ec676ea947cd6c8daa6fcfa351b11e62651e6e06f5dde59bb566d36e6989" + ] + }, + "extrinsicsRoot": "0xeaa9cd48b36a88ba7cf934cdbcd8f2afc0843978912452529ace7ef2da09691d", + "number": "0x67", + "parentHash": "0xf33015565b9978d146cdf648c498649b04c323cd35d9f55fad7d8586d4b42ea2", + "stateRoot": "0xa8b0c74dbf09ee9ff5443076f8298027e3a6505ab6e3f6a683a7d4d137130683" + } + }, + "justification": None + }, + "id": 1 + } + elif method == 'state_getStorageAt': + return {'jsonrpc': '2.0', 'result': '0x04be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f', 'id': 11} + elif method == 'chain_subscribeNewHeads': + return result_handler({ + "jsonrpc": "2.0", + "params": { + "result": { + "digest": { + "logs": ['0x066175726120afe0021000000000', '0x05617572610101567be3d55b4885ce3ac6a7b46b28adf138299acc3eb5f11ffa15c3ed0551f22b7220ec676ea947cd6c8daa6fcfa351b11e62651e6e06f5dde59bb566d36e6989'] + }, + "extrinsicsRoot": "0xeaa9cd48b36a88ba7cf934cdbcd8f2afc0843978912452529ace7ef2da09691d", + "number": "0x67", + "parentHash": "0xf33015565b9978d146cdf648c498649b04c323cd35d9f55fad7d8586d4b42ea2", + "stateRoot": "0xa8b0c74dbf09ee9ff5443076f8298027e3a6505ab6e3f6a683a7d4d137130683" + }, + "subscription": 'test1' + } + }, 0, 'test1') + elif method == 'chain_unsubscribeNewHeads': + return { + "jsonrpc": "2.0", + "result": True + } + elif method == 'rpc_methods': + return { + "jsonrpc": "2.0", + "result": {"methods": ['author_submitExtrinsic', 'author_submitAndWatchExtrinsic', 'author_unwatchExtrinsic', 'author_pendingExtrinsics', 'chain_getBlockHash', 'chain_getHeader', 'chain_getBlock', 'chain_getFinalizedHead', 'chain_subscribeNewHead', 'chain_subscribeFinalizedHeads', 'chain_unsubscribeNewHead', 'chain_subscribeNewHeads', 'chain_unsubscribeNewHeads', 'chain_unsubscribeFinalizedHeads', 'state_getRuntimeVersion', 'state_getMetadata', 'state_getStorage', 'state_getKeysPaged', 'state_queryStorageAt', 'state_call', 'state_subscribeRuntimeVersion', 'state_unsubscribeRuntimeVersion', 'state_subscribeStorage', 'state_unsubscribeStorage', 'system_localPeerId', 'system_nodeRoles', 'system_localListenAddresses', 'system_chain', 'system_properties', 'system_name', 'system_version', 'system_chainType', 'system_health', 'system_dryRun', 'system_accountNextIndex', 'payment_queryFeeDetails', 'payment_queryInfo', 'dev_newBlock', 'dev_setStorage', 'dev_timeTravel', 'dev_setHead', 'dev_dryRun', 'rpc_methods']}, + "id": 1 + } + + raise ValueError(f"Unsupported mocked method {method}") + + cls.substrate.rpc_request = MagicMock(side_effect=mocked_request) + cls.substrate.query = MagicMock(side_effect=mocked_query) + + def test_get_block_by_head(self): + + block = self.substrate.get_block() + self.assertEqual('0xec828914eca09331dad704404479e2899a971a9b5948345dc40abca4ac818f93', block['header']['hash']) + + def test_get_chain_head(self): + block_hash = self.substrate.get_chain_head() + self.assertEqual('0xec828914eca09331dad704404479e2899a971a9b5948345dc40abca4ac818f93', block_hash) + + +if __name__ == '__main__': + unittest.main()