-
Notifications
You must be signed in to change notification settings - Fork 1
Support custom timeouts for txn confirmation #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6d57454
00c7f7e
2cb4541
241b9f0
5963673
1307a06
d16e099
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from aptos_sdk.ed25519 import Signature as Ed25519Signature | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from aptos_sdk.transactions import FeePayerRawTransaction, SignedTransaction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from ._constants import DEFAULT_TXN_CONFIRM_TIMEOUT, DEFAULT_TXN_SUBMIT_TIMEOUT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from ._exceptions import TxnConfirmError, TxnSubmitError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from ._fee_pay import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PendingTransactionResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| submit_fee_paid_transaction, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -170,19 +172,25 @@ async def submit_tx( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transaction: SimpleTransaction, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sender_authenticator: AccountAuthenticator, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| txn_submit_timeout: float | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> PendingTransactionResponse: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if self._no_fee_payer: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await self._submit_direct(transaction, sender_authenticator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await self._submit_direct(transaction, sender_authenticator, txn_submit_timeout) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await submit_fee_paid_transaction( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._config, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transaction, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sender_authenticator, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| txn_submit_timeout=txn_submit_timeout, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def _send_tx( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload: InputEntryFunctionData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| account_override: Account | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| txn_submit_timeout: float | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| txn_confirm_timeout: float | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signer = account_override if account_override is not None else self._account | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sender = signer.address() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -216,9 +224,37 @@ async def _send_tx( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sender_authenticator = self._sign_transaction(signer, transaction) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pending_tx = await self.submit_tx(transaction, sender_authenticator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if txn_submit_timeout is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| txn_submit_timeout = DEFAULT_TXN_SUBMIT_TIMEOUT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await self._wait_for_transaction(pending_tx.hash) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pending_tx = await self.submit_tx( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transaction, sender_authenticator, txn_submit_timeout=txn_submit_timeout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except httpx.ConnectTimeout as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise TxnSubmitError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Failed to submit transaction: connection timeout to {self._config.fullnode_url}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| original_exception=e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except httpx.ConnectError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise TxnSubmitError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Failed to submit transaction: connection error - {e}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| original_exception=e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except httpx.HTTPStatusError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise TxnSubmitError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Failed to submit transaction: HTTP {e.response.status_code}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| original_exception=e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise TxnSubmitError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Failed to submit transaction: {e}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| original_exception=e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await self._wait_for_transaction( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pending_tx.hash, txn_confirm_timeout=txn_confirm_timeout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+255
to
+257
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await self._wait_for_transaction( | |
| pending_tx.hash, txn_confirm_timeout=txn_confirm_timeout | |
| ) | |
| try: | |
| return await self._wait_for_transaction( | |
| pending_tx.hash, txn_confirm_timeout=txn_confirm_timeout | |
| ) | |
| except TxnConfirmError: | |
| raise | |
| except httpx.ConnectTimeout as e: | |
| raise TxnConfirmError( | |
| f"Failed to confirm transaction {pending_tx.hash}: connection timeout to {self._config.fullnode_url}", | |
| original_exception=e, | |
| ) from e | |
| except httpx.ConnectError as e: | |
| raise TxnConfirmError( | |
| f"Failed to confirm transaction {pending_tx.hash}: connection error - {e}", | |
| original_exception=e, | |
| ) from e | |
| except httpx.HTTPStatusError as e: | |
| raise TxnConfirmError( | |
| f"Failed to confirm transaction {pending_tx.hash}: HTTP {e.response.status_code}", | |
| original_exception=e, | |
| ) from e | |
| except Exception as e: | |
| raise TxnConfirmError( | |
| f"Failed to confirm transaction {pending_tx.hash}: {e}", | |
| original_exception=e, | |
| ) from e |
Copilot
AI
Apr 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as the async version: catching httpx.HTTPStatusError here appears unreachable without response.raise_for_status(). Consider removing this handler or changing the submission code to raise HTTPStatusError intentionally for non-success responses.
| except httpx.HTTPStatusError as e: | |
| raise TxnSubmitError( | |
| f"Failed to submit transaction: HTTP {e.response.status_code}", | |
| original_exception=e, | |
| ) from e |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| """Custom exceptions for the Decibel SDK. | ||
|
|
||
| These exceptions help callers distinguish between failures that are safe to retry | ||
| (submission errors) vs failures that require checking transaction status first | ||
| (confirmation errors). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
|
|
||
| class TxnConfirmError(Exception): | ||
| """ | ||
| Transaction was submitted but confirmation failed. | ||
|
|
||
| Causes: | ||
| - Transaction did not confirm within timeout (still pending or dropped) | ||
| - Transaction executed but reverted (VM error) | ||
| - Transaction failed during execution | ||
|
|
||
| CRITICAL: The transaction MAY be on-chain. Check tx_hash status before retrying | ||
| to avoid duplicate transactions. | ||
|
|
||
| Attributes: | ||
| tx_hash: The transaction hash that was submitted | ||
| message: Description of what went wrong | ||
| """ | ||
|
|
||
| def __init__(self, tx_hash: str, message: str) -> None: | ||
| self.tx_hash = tx_hash | ||
| super().__init__(f"Transaction {tx_hash}: {message}") | ||
|
|
||
|
|
||
| class TxnSubmitError(Exception): | ||
| """ | ||
| Transaction submission failed before reaching the blockchain. | ||
|
|
||
| Causes: | ||
| - Network connectivity issues (timeout, connection refused) | ||
| - RPC endpoint unavailable | ||
| - HTTP errors (5xx, 429 rate limit) | ||
| - Serialization errors | ||
|
|
||
| SAFE TO RETRY: The transaction was never submitted to the blockchain. | ||
|
|
||
| Attributes: | ||
| original_exception: The underlying exception that caused the failure | ||
| """ | ||
|
|
||
| def __init__(self, message: str, original_exception: Exception | None = None) -> None: | ||
| self.original_exception = original_exception | ||
| super().__init__(message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
httpx.HTTPStatusError is unlikely to be raised here because none of the submit helpers call response.raise_for_status(); non-2xx responses are converted into ValueError in _submit_direct / _fee_pay. This except block is effectively dead code and can mislead readers; either remove it or switch submission helpers to use response.raise_for_status() consistently.