Skip to content

Commit d2c1ed9

Browse files
committed
Other methods added.
1 parent 19223ac commit d2c1ed9

File tree

3 files changed

+389
-5
lines changed

3 files changed

+389
-5
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
GenericExtrinsic,
3131
GenericRuntimeCallDefinition,
3232
ss58_encode,
33+
MultiAccountId,
3334
)
3435
from websockets.asyncio.client import connect
3536
from websockets.exceptions import ConnectionClosed
@@ -1137,6 +1138,32 @@ async def result_handler(
11371138
result_handler=result_handler,
11381139
)
11391140

1141+
async def retrieve_pending_extrinsics(self) -> list:
1142+
"""
1143+
Retrieves and decodes pending extrinsics from the node's transaction pool
1144+
1145+
Returns:
1146+
list of extrinsics
1147+
"""
1148+
1149+
runtime = await self.init_runtime()
1150+
1151+
result_data = await self.rpc_request("author_pendingExtrinsics", [])
1152+
1153+
extrinsics = []
1154+
1155+
for extrinsic_data in result_data["result"]:
1156+
extrinsic = runtime.runtime_config.create_scale_object(
1157+
"Extrinsic", metadata=runtime.metadata
1158+
)
1159+
extrinsic.decode(
1160+
ScaleBytes(extrinsic_data),
1161+
check_remaining=self.config.get("strict_scale_decode"),
1162+
)
1163+
extrinsics.append(extrinsic)
1164+
1165+
return extrinsics
1166+
11401167
async def get_metadata_storage_functions(self, block_hash=None) -> list:
11411168
"""
11421169
Retrieves a list of all storage functions in metadata active at given block_hash (or chaintip if block_hash is
@@ -1802,7 +1829,7 @@ def convert_event_data(data):
18021829
events.append(convert_event_data(item))
18031830
return events
18041831

1805-
async def get_metadata(self, block_hash=None):
1832+
async def get_metadata(self, block_hash=None) -> MetadataV15:
18061833
"""
18071834
Returns `MetadataVersioned` object for given block_hash or chaintip if block_hash is omitted
18081835
@@ -1815,7 +1842,7 @@ async def get_metadata(self, block_hash=None):
18151842
"""
18161843
runtime = await self.init_runtime(block_hash=block_hash)
18171844

1818-
return runtime.metadata
1845+
return runtime.metadata_v15
18191846

18201847
@a.lru_cache(maxsize=512)
18211848
async def get_parent_block_hash(self, block_hash):
@@ -1833,10 +1860,43 @@ async def _get_parent_block_hash(self, block_hash):
18331860
return block_hash
18341861
return parent_block_hash
18351862

1863+
async def get_storage_by_key(self, block_hash: str, storage_key: str) -> Any:
1864+
"""
1865+
A pass-though to existing JSONRPC method `state_getStorage`/`state_getStorageAt`
1866+
1867+
Args:
1868+
block_hash: hash of the block
1869+
storage_key: storage key to query
1870+
1871+
Returns:
1872+
result of the query
1873+
1874+
"""
1875+
1876+
if await self.supports_rpc_method("state_getStorageAt"):
1877+
response = await self.rpc_request(
1878+
"state_getStorageAt", [storage_key, block_hash]
1879+
)
1880+
else:
1881+
response = await self.rpc_request(
1882+
"state_getStorage", [storage_key, block_hash]
1883+
)
1884+
1885+
if "result" in response:
1886+
return response.get("result")
1887+
elif "error" in response:
1888+
raise SubstrateRequestException(response["error"]["message"])
1889+
else:
1890+
raise SubstrateRequestException(
1891+
"Unknown error occurred during retrieval of events"
1892+
)
1893+
18361894
@a.lru_cache(maxsize=16)
18371895
async def get_block_runtime_info(self, block_hash: str) -> dict:
18381896
return await self._get_block_runtime_info(block_hash)
18391897

1898+
get_block_runtime_version = get_block_runtime_info
1899+
18401900
async def _get_block_runtime_info(self, block_hash: str) -> dict:
18411901
"""
18421902
Retrieve the runtime info of given block_hash
@@ -2591,6 +2651,34 @@ async def create_signed_extrinsic(
25912651

25922652
return extrinsic
25932653

2654+
async def create_unsigned_extrinsic(self, call: GenericCall) -> GenericExtrinsic:
2655+
"""
2656+
Create unsigned extrinsic for given `Call`
2657+
2658+
Args:
2659+
call: GenericCall the call the extrinsic should contain
2660+
2661+
Returns:
2662+
GenericExtrinsic
2663+
"""
2664+
2665+
runtime = await self.init_runtime()
2666+
2667+
# Create extrinsic
2668+
extrinsic = self.runtime_config.create_scale_object(
2669+
type_string="Extrinsic", metadata=runtime.metadata
2670+
)
2671+
2672+
extrinsic.encode(
2673+
{
2674+
"call_function": call.value["call_function"],
2675+
"call_module": call.value["call_module"],
2676+
"call_args": call.value["call_args"],
2677+
}
2678+
)
2679+
2680+
return extrinsic
2681+
25942682
async def get_chain_finalised_head(self):
25952683
"""
25962684
A pass-though to existing JSONRPC method `chain_getFinalizedHead`
@@ -3165,6 +3253,100 @@ async def query_map(
31653253
ignore_decoding_errors=ignore_decoding_errors,
31663254
)
31673255

3256+
async def create_multisig_extrinsic(
3257+
self,
3258+
call: GenericCall,
3259+
keypair: Keypair,
3260+
multisig_account: MultiAccountId,
3261+
max_weight: Optional[Union[dict, int]] = None,
3262+
era: dict = None,
3263+
nonce: int = None,
3264+
tip: int = 0,
3265+
tip_asset_id: int = None,
3266+
signature: Union[bytes, str] = None,
3267+
) -> GenericExtrinsic:
3268+
"""
3269+
Create a Multisig extrinsic that will be signed by one of the signatories. Checks on-chain if the threshold
3270+
of the multisig account is reached and try to execute the call accordingly.
3271+
3272+
Args:
3273+
call: GenericCall to create extrinsic for
3274+
keypair: Keypair of the signatory to approve given call
3275+
multisig_account: MultiAccountId to use of origin of the extrinsic (see `generate_multisig_account()`)
3276+
max_weight: Maximum allowed weight to execute the call ( Uses `get_payment_info()` by default)
3277+
era: Specify mortality in blocks in follow format: {'period': [amount_blocks]} If omitted the extrinsic is
3278+
immortal
3279+
nonce: nonce to include in extrinsics, if omitted the current nonce is retrieved on-chain
3280+
tip: The tip for the block author to gain priority during network congestion
3281+
tip_asset_id: Optional asset ID with which to pay the tip
3282+
signature: Optionally provide signature if externally signed
3283+
3284+
Returns:
3285+
GenericExtrinsic
3286+
"""
3287+
if max_weight is None:
3288+
payment_info = await self.get_payment_info(call, keypair)
3289+
max_weight = payment_info["weight"]
3290+
3291+
# Check if call has existing approvals
3292+
multisig_details_ = await self.query(
3293+
"Multisig", "Multisigs", [multisig_account.value, call.call_hash]
3294+
)
3295+
multisig_details = getattr(multisig_details_, "value", multisig_details_)
3296+
if multisig_details:
3297+
maybe_timepoint = multisig_details["when"]
3298+
else:
3299+
maybe_timepoint = None
3300+
3301+
# Compose 'as_multi' when final, 'approve_as_multi' otherwise
3302+
if (
3303+
multisig_details.value
3304+
and len(multisig_details.value["approvals"]) + 1
3305+
== multisig_account.threshold
3306+
):
3307+
multi_sig_call = await self.compose_call(
3308+
"Multisig",
3309+
"as_multi",
3310+
{
3311+
"other_signatories": [
3312+
s
3313+
for s in multisig_account.signatories
3314+
if s != f"0x{keypair.public_key.hex()}"
3315+
],
3316+
"threshold": multisig_account.threshold,
3317+
"maybe_timepoint": maybe_timepoint,
3318+
"call": call,
3319+
"store_call": False,
3320+
"max_weight": max_weight,
3321+
},
3322+
)
3323+
else:
3324+
multi_sig_call = await self.compose_call(
3325+
"Multisig",
3326+
"approve_as_multi",
3327+
{
3328+
"other_signatories": [
3329+
s
3330+
for s in multisig_account.signatories
3331+
if s != f"0x{keypair.public_key.hex()}"
3332+
],
3333+
"threshold": multisig_account.threshold,
3334+
"maybe_timepoint": maybe_timepoint,
3335+
"call_hash": call.call_hash,
3336+
"max_weight": max_weight,
3337+
},
3338+
)
3339+
3340+
return await self.create_signed_extrinsic(
3341+
multi_sig_call,
3342+
keypair,
3343+
era=era,
3344+
nonce=nonce,
3345+
tip=tip,
3346+
tip_asset_id=tip_asset_id,
3347+
signature=signature,
3348+
)
3349+
31683350
async def submit_extrinsic(
31693351
self,
31703352
extrinsic: GenericExtrinsic,

0 commit comments

Comments
 (0)