From 1c09484c3008601c5e7c82a6a6e58b54fafe23d2 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 28 Nov 2025 12:20:38 +0200 Subject: [PATCH 1/2] Handle 'usurped' error from substrate --- async_substrate_interface/async_substrate.py | 11 ++++++++++- async_substrate_interface/sync_substrate.py | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index c555c7b..b491e5a 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -3987,6 +3987,15 @@ async def result_handler(message: dict, subscription_id) -> tuple[dict, bool]: message_result = { k.lower(): v for k, v in message["params"]["result"].items() } + if "usurped" in message_result: + logger.error( + f"Subscription {subscription_id} usurped: {message_result}" + ) + async with self.ws as ws: + await ws.unsubscribe(subscription_id) + raise SubstrateRequestException( + f"Subscription {subscription_id} usurped: {message_result}" + ) if "finalized" in message_result and wait_for_finalization: logger.debug("Extrinsic finalized. Unsubscribing.") @@ -3998,7 +4007,7 @@ async def result_handler(message: dict, subscription_id) -> tuple[dict, bool]: "finalized": True, }, True elif ( - any(x in message_result for x in ["inblock", "inBlock"]) + "inblock" in message_result and wait_for_inclusion and not wait_for_finalization ): diff --git a/async_substrate_interface/sync_substrate.py b/async_substrate_interface/sync_substrate.py index c175c1f..630d4cd 100644 --- a/async_substrate_interface/sync_substrate.py +++ b/async_substrate_interface/sync_substrate.py @@ -3170,6 +3170,15 @@ def result_handler(message: dict, subscription_id) -> tuple[dict, bool]: k.lower(): v for k, v in message["params"]["result"].items() } + if "usurped" in message_result: + logger.error( + f"Subscription {subscription_id} usurped: {message_result}" + ) + self.rpc_request("author_unwatchExtrinsic", [subscription_id]) + raise SubstrateRequestException( + f"Subscription {subscription_id} usurped: {message_result}" + ) + if "finalized" in message_result and wait_for_finalization: # Created as a task because we don't actually care about the result # TODO change this logic From 73b65868b4e8d0b1cc48fe2a1a34d61cc15e110f Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 28 Nov 2025 13:14:15 +0200 Subject: [PATCH 2/2] Handle all subscription update failures --- async_substrate_interface/async_substrate.py | 25 ++++++++++++++++---- async_substrate_interface/sync_substrate.py | 25 ++++++++++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/async_substrate_interface/async_substrate.py b/async_substrate_interface/async_substrate.py index b491e5a..25b6791 100644 --- a/async_substrate_interface/async_substrate.py +++ b/async_substrate_interface/async_substrate.py @@ -3987,15 +3987,32 @@ async def result_handler(message: dict, subscription_id) -> tuple[dict, bool]: message_result = { k.lower(): v for k, v in message["params"]["result"].items() } + # check for any subscription indicators of failure + failure_message = None if "usurped" in message_result: - logger.error( + failure_message = ( f"Subscription {subscription_id} usurped: {message_result}" ) + if "retracted" in message_result: + failure_message = ( + f"Subscription {subscription_id} retracted: {message_result}" + ) + if "finalitytimeout" in message_result: + failure_message = f"Subscription {subscription_id} finalityTimeout: {message_result}" + if "dropped" in message_result: + failure_message = ( + f"Subscription {subscription_id} dropped: {message_result}" + ) + if "invalid" in message_result: + failure_message = ( + f"Subscription {subscription_id} invalid: {message_result}" + ) + + if failure_message is not None: async with self.ws as ws: await ws.unsubscribe(subscription_id) - raise SubstrateRequestException( - f"Subscription {subscription_id} usurped: {message_result}" - ) + logger.error(failure_message) + raise SubstrateRequestException(failure_message) if "finalized" in message_result and wait_for_finalization: logger.debug("Extrinsic finalized. Unsubscribing.") diff --git a/async_substrate_interface/sync_substrate.py b/async_substrate_interface/sync_substrate.py index 630d4cd..a6c0be0 100644 --- a/async_substrate_interface/sync_substrate.py +++ b/async_substrate_interface/sync_substrate.py @@ -3170,14 +3170,31 @@ def result_handler(message: dict, subscription_id) -> tuple[dict, bool]: k.lower(): v for k, v in message["params"]["result"].items() } + # check for any subscription indicators of failure + failure_message = None if "usurped" in message_result: - logger.error( + failure_message = ( f"Subscription {subscription_id} usurped: {message_result}" ) - self.rpc_request("author_unwatchExtrinsic", [subscription_id]) - raise SubstrateRequestException( - f"Subscription {subscription_id} usurped: {message_result}" + if "retracted" in message_result: + failure_message = ( + f"Subscription {subscription_id} retracted: {message_result}" + ) + if "finalitytimeout" in message_result: + failure_message = f"Subscription {subscription_id} finalityTimeout: {message_result}" + if "dropped" in message_result: + failure_message = ( + f"Subscription {subscription_id} dropped: {message_result}" ) + if "invalid" in message_result: + failure_message = ( + f"Subscription {subscription_id} invalid: {message_result}" + ) + + if failure_message is not None: + self.rpc_request("author_unwatchExtrinsic", [subscription_id]) + logger.error(failure_message) + raise SubstrateRequestException(failure_message) if "finalized" in message_result and wait_for_finalization: # Created as a task because we don't actually care about the result