Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Initial version of Tx Queue#53

Open
xmariachi wants to merge 6 commits into
devfrom
diego/engn-5614-sdk-py-create-submission-queue-component
Open

Initial version of Tx Queue#53
xmariachi wants to merge 6 commits into
devfrom
diego/engn-5614-sdk-py-create-submission-queue-component

Conversation

@xmariachi

@xmariachi xmariachi commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

Purpose

Adds a Generic Transaction Submission Queue Component for the Python SDK.

This branch introduces a reusable queue with priority/deadline scheduling, per-account sequential processing, retry handling, and sequence-mismatch recovery (invalidate + refetch). It also adds focused unit tests for ordering, retries, deadline behavior, stop/drain semantics, and same-account vs cross-account execution.

Goal

Provide a foundation for queue-backed transaction submission while preserving backward compatibility for existing submission paths.

Properties

Priority-aware scheduling
Optional deadline-based dispatch/fail-fast behavior
Strict per-account sequencing
Sequence cache invalidation + refetch on mismatch
Retry/backoff for retryable failures
Typed queue errors (deadline exceeded, retries exhausted, queue stopped)
Async awaitable result handle
Configurable queue bounds (priority range, backoff, starvation boost)

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cubic analysis

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/allora_sdk/rpc_client/tx_queue.py">

<violation number="1" location="src/allora_sdk/rpc_client/tx_queue.py:260">
P1: FIFO tiebreaking is broken: `_sequence_counter` is tracked but never included in the sort key, so items with identical deadline/priority/`created_at` have non-deterministic ordering. The `sequence_no` should be appended to the sort-key tuple as the final tiebreaker.

According to linked Linear issue ENGN-5614, the required ordering is `deadline_at -> priority -> FIFO`.</violation>
</file>

Linked issue analysis

Linked issue: ENGN-5614: SDK py: create submission queue component

Status Acceptance criteria Notes
priority/deadline/FIFO ordering Sort key and scheduling implement deadline>priority>FIFO
per-account strict sequencing Single account worker ensures one-consumer sequencing
concurrent progress across different accounts Different account workers run concurrently and tested
bounded retries with backoff/jitter Backoff calc + jitter and retry loop present
sequence invalidation + refetch on mismatch SequenceState.invalidate + adapter.fetch_sequence used on mismatch
TxManager exposes queue-backed enqueue path without breaking existing submit_transaction(...) No TxManager changes or integration code in diffs
At least worker/reputer path can submit through queue with priority/deadline No worker/reputer integration present in PR
Stop semantics are defined and tested (cancel_pending=True/False) stop(cancel_pending) implemented and both behaviors tested
Unit tests cover success/failure paths and typed exceptions Comprehensive unit tests for ordering, retries, mismatch, timeouts
Docs include usage guidance and migration notes No documentation files or docs changes present
Typed adapter contract (Protocol) for fetch_sequence/submit/classify_error TxQueueAdapter Protocol defines required adapter API
Awaitable pending handle compatible with current async call patterns PendingQueueTx implements awaitable wait and tests await it
Starvation mitigation via age-based priority boost compute_effective_priority implements age-based boosts
Scheduler + per-account workers implemented Scheduler loop and per-account worker tasks exist
Sequence cache with atomic invalidate/advance operations SequenceState provides current_or_fetch, invalidate, advance

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/allora_sdk/rpc_client/tx_queue.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/allora_sdk/__init__.py">

<violation number="1" location="src/allora_sdk/__init__.py:34">
P2: Module `__getattr__` doesn't cache resolved attributes. Every access to a lazy-loaded name (e.g., `allora_sdk.AlloraRPCClient`) re-enters `__getattr__`, repeating string comparisons and `import_module`/`getattr` calls. The standard PEP 562 pattern stores the result in `globals()` so `__getattr__` is bypassed on subsequent lookups.</violation>
</file>

<file name="src/allora_sdk/rpc_client/__init__.py">

<violation number="1" location="src/allora_sdk/rpc_client/__init__.py:24">
P2: Module-level `__getattr__` does not cache resolved attributes in `globals()`. Per the standard PEP 562 lazy-loading pattern, after resolving an attribute you should store it with `globals()[name] = val` so subsequent lookups go through normal attribute access and bypass `__getattr__`. Without this, repeated attribute access via `rpc_client.AlloraRPCClient` re-invokes `__getattr__` every time.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/allora_sdk/__init__.py
Comment thread src/allora_sdk/rpc_client/__init__.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/allora_sdk/rpc_client/tx_manager.py">

<violation number="1" location="src/allora_sdk/rpc_client/tx_manager.py:498">
P1: `_submit_via_queue` does not invalidate the gas price cache or escalate fees on `InsufficientFeesError`, unlike the old retry path which clears `_cached_gas_price`/`_gas_price_cache_time` and applies `fee_multiplier = 1.0 + attempt * 0.5`. Queue retries will repeatedly submit with the same stale gas price and base fee multiplier.</violation>

<violation number="2" location="src/allora_sdk/rpc_client/tx_manager.py:504">
P1: Queue-backed retries for `OutOfGasError` are ineffective: `gas_multiplier` is hardcoded to `1.0` with no escalation across attempts. The old `_attempt_submissions` path increases gas by 30% per attempt (`1.0 + attempt * 0.3`), but the queue adapter has no mechanism to escalate. Consider tracking the attempt number in the payload or the adapter, or passing it via the submission context.</violation>
</file>

<file name="src/allora_sdk/rpc_client/client_emissions.py">

<violation number="1" location="src/allora_sdk/rpc_client/client_emissions.py:45">
P2: Return type `Union[PendingTx, int]` is incorrect when `use_queue=True` — `submit_transaction` returns a `PendingQueueTx` in that case, which is a separate class with a different interface. The annotation should be updated to `Union[PendingTx, PendingQueueTx, int]` (or a shared protocol) for all three methods that now accept `use_queue`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/allora_sdk/rpc_client/tx_manager.py Outdated
Comment thread src/allora_sdk/rpc_client/tx_manager.py Outdated
Comment thread src/allora_sdk/rpc_client/client_emissions.py
@spooktheducks

Copy link
Copy Markdown
Contributor

Code Review Summary

Multi-agent review — 5 reviewers + adversarial challenge

Linear project: https://linear.app/alloralabs/project/pr-review-allora-networkallora-sdk-py-53-initial-version-of-tx-queue-c10b17cdd10d

🔴 Must Fix

  • On-chain tx errors (OutOfGas, InsufficientFees) consume sequence but queue doesn't advance or invalidate cache, guaranteeing a wasted retry (src/allora_sdk/rpc_client/tx_queue.py:347)
    When _raise_for_status raises OutOfGasError or InsufficientFeesError after wait_for_tx confirms block inclusion, the transaction consumed the on-chain sequence number. However, _process_item only calls _sequence_state.advance() after a fully successful submit() return — the exception propagates before advance is reached. The error is classified as RETRYABLE (not SEQUENCE_MISMATCH), so the cache is not invalidated either. The next retry reuses the stale cached sequence, which immediately fails with AccountSequenceMismatchError, wasting an entire retry attempt and round-trip. Both adversarial agents independently identified this, and both verifiers confirmed it.

    Suggestion: Either (a) have _submit_via_queue advance the sequence cache when raising on-chain errors (since the sequence was consumed), (b) classify on-chain errors as SEQUENCE_MISMATCH so invalidate+refetch occurs, or (c) add a new error classification for retryable-with-sequence-consumed.

  • AlloraRPCClient.close() will crash with AttributeError on REST-only or no-websocket configurations (src/allora_sdk/rpc_client/client.py:169)
    self.events is only set when self.network.websocket_url is not None. self.grpc_client is only set in the gRPC protocol branch. Neither has a class-level None default. close() accesses them unconditionally — raising AttributeError for REST-only or no-websocket clients. The PR adds a new call path (AlloraWorker._cleanupAlloraRPCClient.close()), making this a concrete production crash. Both verifiers confirmed as must-fix.

    Suggestion: Add class-level defaults (events: Optional[...] = None, grpc_client: Optional[...] = None) or use getattr(self, 'events', None) guards in close().

  • Successful submit followed by advance() failure silently loses the success result (src/allora_sdk/rpc_client/tx_queue.py:355)
    In _process_item, if adapter.submit() succeeds but _sequence_state.advance() then raises an exception, control falls to the except Exception block, which classifies it as FATAL and calls set_exception on the future. The caller sees a failure for a transaction that was already successfully included on-chain. The success result is irrecoverably lost. Both verifiers confirmed this.

    Suggestion: Set the future's result immediately after submit() returns successfully, before calling advance(). Wrap advance() in its own try/except that logs the error but does not overwrite the resolved future.

🟡 Should Fix

  • Redundant account_info RPC on every broadcast in queue path + misleading debug log (src/allora_sdk/rpc_client/tx_manager.py:572)
    _build_and_broadcast unconditionally calls auth_client.account_info on every invocation, even when the queue path already provides the sequence. account_number is stable for a wallet's lifetime — re-fetching it wastes a network round-trip per submission. Additionally, the debug log prints info.sequence (the chain value) rather than sequence_number (the value actually used for signing). After sequence-mismatch recovery these differ, actively misleading debugging.

    Suggestion: Cache account_number in TxManager (fetched once per address, invalidated on auth errors). Fix the log: logger.debug(f"Account info: chain_seq={info.sequence}, used_seq={sequence_number}, num={info.account_number}")

  • _pre_flight_checks called on every queue retry adds 2 extra RPC calls per attempt (src/allora_sdk/rpc_client/tx_manager.py:508)
    _submit_via_queue calls await self._pre_flight_checks() unconditionally on every attempt including retries. This makes 2 RPC calls (auth_client.account() + bank_client.balance()) per attempt. Combined with the unconditional account_info in _build_and_broadcast, each queue submission attempt makes at least 3 overhead RPCs. Both verifiers confirmed.

    Suggestion: Run pre-flight checks only on the first attempt (check payload.retry_attempt == 0), or move the check out of adapter.submit and into _account_worker before the retry loop.

  • Inconsistent API surface: queue parameters missing from create_topic, fund_topic, and bulk_add_to_topic_ methods* (src/allora_sdk/rpc_client/client_emissions.py:247)
    register, insert_worker_payload, and delegate_stake were updated with queue parameters, while create_topic, fund_topic, bulk_add_to_topic_worker_whitelist, and bulk_add_to_topic_reputer_whitelist were not. A caller who expects all submissions to go through the queue will silently get direct-path PendingTx objects for these methods, breaking per-account sequential ordering. The return type inconsistency also affects type safety.

    Suggestion: Either add queue parameters to all remaining methods, or document the omission explicitly with a TODO and add a guard/warning.

  • TxManager.close() nulls queue but doesn't prevent re-initialization via concurrent submit_transaction(use_queue=True) (src/allora_sdk/rpc_client/tx_manager.py:321)
    close() sets self._tx_queue = None after stopping. A concurrent call to submit_transaction(use_queue=True) can race and call _ensure_tx_queue_started(), which sees None and creates a new TransactionQueue, defeating the purpose of close(). Both verifiers confirmed.

    Suggestion: Add a _closed: bool = False flag. Set it in close() under lock. Check it in _ensure_tx_queue_started() and raise if closed.

  • OutOfGasError handling in queue path lacks explicit branch — fragile under change (src/allora_sdk/rpc_client/tx_manager.py:541) ⚠️ contested: Both adversarial agents agreed the finding was understated — the implicit behavior masks a deeper sequence-cache bug covered in the must-fix finding. The gas escalation itself works, but the asymmetric exception handling is genuinely fragile.
    In _submit_via_queue, OutOfGasError falls through to the broad except Exception clause. The direct path has an explicit except OutOfGasError branch. The implicit behavior is currently equivalent but fragile under change. This is also the natural location to fix the on-chain sequence consumption issue (see must-fix).

    Suggestion: Add an explicit except OutOfGasError branch in _submit_via_queue that mirrors the direct path. This becomes the natural place to also advance the sequence cache since the on-chain sequence was consumed.

  • Race condition on parent_tx_id can produce duplicate request IDs (src/allora_sdk/rpc_client/tx_manager.py:291) ⚠️ contested: Both adversarial agents confirmed the race exists but noted impact is limited to debug messages currently. Downgraded from medium-security to should-fix correctness.
    Between reading self.parent_tx_id (line 291) and incrementing it (line 299), there are two await points, allowing another coroutine to read the same value. This produces duplicate request_id strings, making debugging ambiguous and potentially breaking future keying.

    Suggestion: Generate unique IDs with uuid.uuid4().hex[:12] or protect the read-and-increment under _tx_queue_lock.

🔵 Consider

  • Mutable payload (_QueuedSubmission) used as per-retry state bag (src/allora_sdk/rpc_client/tx_manager.py:148) ⚠️ contested: Both adversarial agents agreed this is overstated for private API. The suggested fix (adapter-owned Dict) adds complexity for a single-implementor case.
    _QueuedSubmission is mutated in-place across retries (retry_attempt, fee_multiplier_override), which is surprising for the TxQueueAdapter.submit() interface. However, both types are private to the same module.

    Suggestion: Document the mutation contract on _QueuedSubmission. If the TxQueueAdapter Protocol is ever made public, move retry state out of the payload.

  • stop(cancel_pending=True) resolves futures before cancelling worker tasks (src/allora_sdk/rpc_client/tx_queue.py:160) ⚠️ contested: Both adversarial agents called this overstated. The done() guards ensure correctness, CancelledError is BaseException in the project's minimum Python 3.10, and the asyncio single-threaded model narrows the window. The concern is a design preference, not a bug.
    Inflight futures are set to TxQueueStoppedError before worker tasks are cancelled. A caller awaiting the handle could theoretically react to the error while the worker task is still running a network request. The done() guards ensure no double-resolution, and CancelledError as BaseException escapes the except Exception handler correctly in Python 3.10+.

    Suggestion: Consider reversing the ordering: cancel/gather worker tasks first, then resolve remaining futures.

  • Inline try/except ModuleNotFoundError fallback stubs create a second maintenance surface (src/allora_sdk/rpc_client/tx_manager.py:19)
    The fallback Protocol stubs and TxResponse dataclass in tx_manager.py exist only for test isolation but can silently drift from real types. The fallback BroadcastMode.SYNC = 'sync' differs from the real integer-backed enum. No test verifies these fallbacks against real type usage.

    Suggestion: Consider moving fallbacks to a dedicated _fallbacks.py or handling test isolation via conftest monkeypatching. At minimum, add a warning log when fallbacks are activated and add a docstring to the fallback TxResponse.

  • Lazy getattr in init.py doesn't benefit 'from allora_sdk import X' patterns (src/allora_sdk/__init__.py:4)
    Python resolves from X import Y by calling X.__getattr__(Y) eagerly at the from-statement. The primary usage pattern in examples and user code is from allora_sdk import AlloraWorker, which still eagerly loads all dependencies. The module docstring's claim about lazy loading is only accurate for import allora_sdk; allora_sdk.X patterns.

    Suggestion: Update the module docstring to clarify the limitation.

  • Add comment explaining defaultdict(asyncio.Lock) safety in Python 3.10+ (src/allora_sdk/rpc_client/tx_queue.py:94)
    The defaultdict(asyncio.Lock) pattern diverges from the rest of the SDK (explicit instance attributes). It is safe in Python ≥3.10 but relies on behavior that a maintainer might not know about.

    Suggestion: Add inline comment: # asyncio.Lock() is safe to construct lazily since Python 3.10 (no longer binds to a loop at construction time).

  • PendingQueueTx.created_at is dead code (src/allora_sdk/rpc_client/tx_queue.py:79)
    PendingQueueTx.created_at is set in __init__ but never read anywhere in the codebase. QueueItem already tracks created_at via the injected now_fn.

    Suggestion: Remove self.created_at = datetime.now() or document its intended external use.

  • _QueuedSubmission defined before FeeTier requires unnecessary forward reference (src/allora_sdk/rpc_client/tx_manager.py:147)
    _QueuedSubmission is defined before FeeTier, requiring a forward-reference string "FeeTier". Moving it after the exception classes (just before _TxManagerQueueAdapter) would eliminate the forward reference and follow the existing convention.

    Suggestion: Move _QueuedSubmission to after the exception classes, just before _TxManagerQueueAdapter.

  • getattr dispatch uses inconsistent branch style (src/allora_sdk/__init__.py:34)
    Single-symbol branches use if name == "X": while multi-symbol branches use if name in {"X", "Y"}:. Using the in {…} form uniformly would make the dispatch table consistent.

    Suggestion: Standardize all branches to if name in {"..."}: form.

  • Duplicate import of cast inside _build_and_broadcast (src/allora_sdk/rpc_client/tx_manager.py:596)
    There is a duplicate from typing import cast inside _build_and_broadcast when cast is already imported at the top-level.

    Suggestion: Remove the inline from typing import cast inside _build_and_broadcast.

💬 Additional Inline Comments

These reference lines outside the diff and could not be posted as inline review comments.

  • 🟡 src/allora_sdk/rpc_client/client_emissions.py:247create_topic, fund_topic, bulk_add_to_topic_worker_whitelist, and bulk_add_to_topic_reputer_whitelist are missing queue parameters. Three other methods in this class were updated with use_queue/queue_priority/queue_deadline_at. A caller who expects all submissions to go through the queue will silently get direct-path PendingTx objects for these methods, breaking per-account sequential ordering guarantees. The return type Union[PendingTx, int] is also narrower than the Union[TxSubmissionHandle, int] used by updated methods.

self.adapter.submit(item.payload, sequence),
timeout=item.timeout.total_seconds(),
)
await self._sequence_state.advance(item.account_id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 On-chain errors (OutOfGas, InsufficientFees) consume the on-chain sequence but the queue doesn't advance or invalidate the cache. When _raise_for_status raises after wait_for_tx confirms block inclusion, the sequence was consumed on-chain. But _process_item only calls advance() after a fully successful submit() return — the exception propagates before advance is reached. The error is classified as RETRYABLE (not SEQUENCE_MISMATCH), so the cache is never invalidated. The next retry reuses the stale cached sequence, which immediately fails with AccountSequenceMismatchError, wasting an entire retry attempt.

Fix: advance the sequence cache (or invalidate it) for on-chain errors that consumed the sequence before retrying.

Comment on lines 164 to 169
async def close(self):
"""Close client and cleanup resources."""
logger.debug("Closing Allora client")
if self.tx_manager is not None:
await self.tx_manager.close(cancel_pending_queue=False)
if self.events:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 self.events and self.grpc_client have no class-level None defaults. self.events is only set when websocket_url is provided; self.grpc_client is only set in the gRPC branch. Calling close() on a REST-only or no-websocket client will raise AttributeError. The PR adds AlloraWorker._cleanup → AlloraRPCClient.close(), making this a concrete production crash path.

[architecture, adversarial-a, adversarial-b — both verifiers confirmed as must-fix]

Suggested change
async def close(self):
"""Close client and cleanup resources."""
logger.debug("Closing Allora client")
if self.tx_manager is not None:
await self.tx_manager.close(cancel_pending_queue=False)
if self.events:
async def close(self):
"""Close client and cleanup resources."""
logger.debug("Closing Allora client")
if self.tx_manager is not None:
await self.tx_manager.close(cancel_pending_queue=False)
if getattr(self, 'events', None):
await self.events.stop()
if getattr(self, 'grpc_client', None):
self.grpc_client.close()

Comment on lines +350 to +357
else:
result = await asyncio.wait_for(
self.adapter.submit(item.payload, sequence),
timeout=item.timeout.total_seconds(),
)
await self._sequence_state.advance(item.account_id)
if not item.handle._final_future.done():
item.handle._final_future.set_result(result)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Successful submit followed by advance() failure silently loses the success result. If adapter.submit() (line 350) returns successfully but advance() (line 355) raises, the exception falls to except Exception, gets classified as FATAL, and set_exception is called on the future. The caller sees a failure for a transaction that was already included on-chain. The success is irrecoverably lost.

Fix: set the future's result immediately after submit() succeeds, before calling advance(). Wrap advance() in its own try/except that logs the error but does not overwrite the already-resolved future.

Suggested change
else:
result = await asyncio.wait_for(
self.adapter.submit(item.payload, sequence),
timeout=item.timeout.total_seconds(),
)
await self._sequence_state.advance(item.account_id)
if not item.handle._final_future.done():
item.handle._final_future.set_result(result)
if item.timeout is None:
result = await self.adapter.submit(item.payload, sequence)
else:
result = await asyncio.wait_for(
self.adapter.submit(item.payload, sequence),
timeout=item.timeout.total_seconds(),
)
if not item.handle._final_future.done():
item.handle._final_future.set_result(result)
try:
await self._sequence_state.advance(item.account_id)
except Exception:
logger.warning(f"Failed to advance sequence cache for {item.account_id} after successful submit", exc_info=True)
return

info = resp.info
sequence_number = info.sequence if sequence is None else sequence
logger.debug(f"Account info: seq={info.sequence}, num={info.account_number}")

@spooktheducks spooktheducks Mar 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Redundant account_info RPC on every broadcast + misleading debug log. account_number is stable for a wallet's lifetime — re-fetching it via RPC on every broadcast wastes a network round-trip per submission. Additionally, this log prints info.sequence (the chain's live value) rather than sequence_number (the value actually used for signing). After sequence-mismatch recovery these differ, actively misleading debugging.

[architecture, correctness, performance — all confirmed by both adversarial agents]

return self._tx_queue

async def _submit_via_queue(self, payload: _QueuedSubmission, sequence: Optional[int]) -> TxResponse:
await self._pre_flight_checks()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 _pre_flight_checks() is called unconditionally on every attempt including retries. This makes 2 RPC calls (auth_client.account() + bank_client.balance()) per attempt. Combined with the unconditional account_info call in _build_and_broadcast, each queue submission attempt makes at least 3 overhead RPCs. For N items with R retries each, this is N*(R+1)*3 extra calls.

[adversarial-a — both verifiers confirmed as should-fix]

fee_tier=fee_tier,
)
queue_handle = await tx_queue.enqueue(
request_id=f"tx-{self.parent_tx_id}",

@spooktheducks spooktheducks Mar 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Race condition: parent_tx_id read/increment spans two await points. Between reading at line 291 and incrementing at line 299, _ensure_tx_queue_started() and enqueue() both yield, allowing interleaving. Two concurrent callers can get the same request_id.


@dataclass
class TxResponse:
code: int = 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Add a docstring to the fallback TxResponse. The fallback _RequestMessage above has a docstring explaining its purpose, but this TxResponse does not. Both exist for the same reason (test-environment compatibility without generated protos).

Suggested change
code: int = 0
@dataclass
class TxResponse:
"""Minimal TxResponse used when generated protos are unavailable."""
code: int = 0
raw_log: str = ""
txhash: str = ""
codespace: str = ""

@@ -195,7 +214,10 @@ async def delegate_stake(
fee_tier: FeeTier = FeeTier.STANDARD,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 delegate_stake has no docstring despite receiving the same three queue parameters and return-type change as register and insert_worker_payload. The new parameters are completely undocumented here.


def __init__(self) -> None:
self._sequences: Dict[str, int] = {}
self._locks: Dict[str, asyncio.Lock] = defaultdict(asyncio.Lock)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Add a comment explaining the Python 3.10+ safety of defaultdict(asyncio.Lock). This pattern diverges from the rest of the SDK (explicit asyncio.Lock() in __init__). It is correct for Python ≥3.10 since Lock() no longer binds to an event loop at construction time — but this is non-obvious and could be 'fixed' incorrectly by a future maintainer.

Suggested change
self._locks: Dict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
# asyncio.Lock() is safe to construct lazily since Python 3.10 (no longer binds to a loop at construction time).
self._locks: Dict[str, asyncio.Lock] = defaultdict(asyncio.Lock)


class PendingQueueTx(Generic[TResult]):
def __init__(self) -> None:
self.created_at = datetime.now()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 self.created_at is assigned but never read anywhere in the codebase. QueueItem already tracks created_at using the injected now_fn. Remove this dead state or document its intended external use.

Suggested change
self.created_at = datetime.now()
self._final_future: asyncio.Future[TResult] = asyncio.get_running_loop().create_future()

@zale144 zale144 removed their request for review April 9, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants