-
Notifications
You must be signed in to change notification settings - Fork 35
feat: add sim_cancelTransaction RPC endpoint with auth and UI #1444
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
e762dfc
99c1373
c621bec
ff66278
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 |
|---|---|---|
|
|
@@ -684,6 +684,133 @@ def admin_upgrade_contract_code( | |
| } | ||
|
|
||
|
|
||
| ####### CANCEL TRANSACTION ENDPOINTS ####### | ||
| def cancel_transaction( | ||
| session: Session, | ||
| transaction_hash: str, | ||
| msg_handler, | ||
| signature: str | None = None, | ||
| admin_key: str | None = None, | ||
| ) -> dict: | ||
| """ | ||
| Cancel a pending or activated transaction. Returns immediately with status. | ||
|
|
||
| Access control: | ||
| - Local (no env vars): open access | ||
| - Hosted/Self-hosted: admin_key allows ANY transaction, signature allows own transactions | ||
|
|
||
| Args: | ||
| session: Database session | ||
| transaction_hash: Hash of the transaction to cancel | ||
| msg_handler: Message handler for WebSocket notifications | ||
| signature: Hex-encoded signature from tx sender (required in hosted mode unless admin_key) | ||
| admin_key: Admin API key for full access to any transaction | ||
|
|
||
| Returns: | ||
| dict with transaction_hash and status | ||
| """ | ||
| from backend.database_handler.models import Transactions | ||
| from eth_account.messages import encode_defunct | ||
| from eth_account import Account | ||
| from web3 import Web3 | ||
| import os | ||
|
|
||
| # Validate transaction hash format | ||
| if ( | ||
| not transaction_hash | ||
| or not transaction_hash.startswith("0x") | ||
| or len(transaction_hash) != 66 | ||
| ): | ||
| raise JSONRPCError( | ||
| code=-32602, | ||
| message="Invalid transaction hash format", | ||
| data={}, | ||
| ) | ||
|
|
||
| # Look up the transaction | ||
| transaction = ( | ||
| session.query(Transactions).filter_by(hash=transaction_hash).one_or_none() | ||
| ) | ||
| if not transaction: | ||
| raise NotFoundError( | ||
| message="Transaction not found", | ||
| data={"transaction_hash": transaction_hash}, | ||
| ) | ||
|
|
||
| is_hosted = os.getenv("VITE_IS_HOSTED") == "true" | ||
| admin_api_key = os.getenv("ADMIN_API_KEY") | ||
|
|
||
| # Check if authorization is needed (hosted or self-hosted with key configured) | ||
| needs_auth = is_hosted or admin_api_key | ||
|
|
||
| if needs_auth: | ||
| # Option 1: Admin key grants full access to ANY transaction | ||
| if admin_api_key and admin_key == admin_api_key: | ||
| pass # Authorized - proceed with cancel | ||
|
|
||
| # Option 2: Signature from tx sender grants access to own transactions | ||
| elif signature: | ||
| if not transaction.from_address: | ||
| raise JSONRPCError( | ||
| code=-32000, | ||
| message="Transaction has no sender - only admin key can cancel", | ||
| data={}, | ||
| ) | ||
|
|
||
| try: | ||
| # Message: keccak256("cancel_transaction" + tx_hash_bytes) | ||
| # tx_hash is unique, so no nonce needed for replay protection | ||
| message_hash = Web3.keccak( | ||
| b"cancel_transaction" + Web3.to_bytes(hexstr=transaction_hash) | ||
| ) | ||
| message = encode_defunct(primitive=message_hash) | ||
| signer = Account.recover_message(message, signature=signature) | ||
|
|
||
| if signer.lower() != transaction.from_address.lower(): | ||
| raise JSONRPCError( | ||
| code=-32000, | ||
| message="Only transaction sender can cancel", | ||
| data={"signer": signer, "sender": transaction.from_address}, | ||
| ) | ||
| except JSONRPCError: | ||
| raise | ||
| except Exception as e: | ||
| raise JSONRPCError( | ||
| code=-32000, | ||
| message=f"Invalid signature: {e!s}", | ||
| data={}, | ||
| ) from e | ||
| else: | ||
| raise JSONRPCError( | ||
| code=-32000, | ||
| message="Cancel requires admin key or sender signature", | ||
| data={}, | ||
| ) | ||
|
|
||
| # Atomic cancel - only succeeds if tx is still pending/activated and not claimed by worker | ||
| was_cancelled = TransactionsProcessor.cancel_transaction_if_available( | ||
| session, transaction_hash | ||
| ) | ||
|
|
||
| if not was_cancelled: | ||
| raise JSONRPCError( | ||
| code=-32000, | ||
| message="Transaction cannot be cancelled: already being processed or in a terminal state", | ||
| data={ | ||
| "transaction_hash": transaction_hash, | ||
| "status": transaction.status.value, | ||
| }, | ||
| ) | ||
|
Comment on lines
+795
to
+803
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error data may show a stale
🛠️ Proposed fix — re-fetch status on failure if not was_cancelled:
+ # Re-query to get the current (post-race) status for an accurate error payload
+ session.refresh(transaction)
raise JSONRPCError(
code=-32000,
message="Transaction cannot be cancelled: already being processed or in a terminal state",
data={
"transaction_hash": transaction_hash,
"status": transaction.status.value,
},
)🤖 Prompt for AI Agents |
||
|
|
||
| # Notify frontend via WebSocket | ||
| msg_handler.send_transaction_status_update(transaction_hash, "CANCELED") | ||
|
|
||
| return { | ||
| "transaction_hash": transaction_hash, | ||
| "status": "CANCELED", | ||
| } | ||
|
Comment on lines
+687
to
+811
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify the number of `cancel_transaction` function definitions in the endpoints file
echo "=== Checking for duplicate cancel_transaction definitions ==="
rg -n "^def cancel_transaction" backend/protocol_rpc/endpoints.py
echo ""
echo "=== Line count and context around lines 687-811 ==="
wc -l backend/protocol_rpc/endpoints.pyRepository: genlayerlabs/genlayer-studio Length of output: 253 🏁 Script executed: #!/bin/bash
# Get the full cancel_transaction function and surrounding context
sed -n '685,815p' backend/protocol_rpc/endpoints.py | cat -nRepository: genlayerlabs/genlayer-studio Length of output: 5495 🏁 Script executed: #!/bin/bash
# Check module-level imports of os in the file
head -80 backend/protocol_rpc/endpoints.py | grep -n "^import os\|^from os"Repository: genlayerlabs/genlayer-studio Length of output: 84 Add type hint to Line 691: Lines 719–728: Transaction hash validation checks only the format ( Additional minor improvements:
🧰 Tools🪛 Ruff (0.15.2)[warning] 770-774: Abstract (TRY301) 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| ####### GEN ENDPOINTS ####### | ||
| async def get_contract_schema( | ||
| accounts_manager: AccountsManager, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.