Skip to content

Transaction-status polling loops in internal/soroban only check their deadline once per 2-second ticker tick #204

Description

@Jagadeeshftw

📌 Description

internal/soroban/rpc.go's pollTransactionStatusOnce and internal/soroban/tx.go's WaitForConfirmation both implement the same polling pattern:

deadline := time.Now().Add(timeout)
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

for {
    select {
    case <-ctx.Done():
        return nil, ctx.Err()
    case <-ticker.C:
        if time.Now().After(deadline) {
            return nil, fmt.Errorf("timeout waiting for transaction...")
        }
        // ... do one poll attempt ...
    }
}

Because the deadline is only ever checked inside the <-ticker.C branch, and time.NewTicker(2 * time.Second) doesn't fire until 2 seconds have elapsed, a caller that passes a timeout shorter than 2 seconds (e.g. timeout: 500 * time.Millisecond in a fast unit/integration test, or a deliberately tight caller-side budget) never gets a timeout error at all within the requested window — the function keeps waiting for the first tick, checks the deadline, finds it already passed, and only then returns the timeout error, roughly 1.5+ seconds later than requested. internal/soroban/rpc.go has no test file today, so this has never been exercised directly (only indirectly through higher-level Soroban tests that use longer timeouts).

🧩 Requirements and context

  • Both polling loops should honor timeout promptly regardless of how short it is relative to the fixed 2-second poll interval — the returned timeout error should not be reliably delayed by up to one extra poll interval beyond the requested deadline.
  • Prefer deriving a context.WithTimeout(ctx, timeout) internally (or wiring the existing ticker against a proper deadline-aware timer) rather than only checking wall-clock time inside the ticker branch.
  • Preserve existing behavior otherwise: still poll roughly every 2 seconds, still return ctx.Err() promptly if the caller's own context is cancelled independent of timeout.

🛠️ Suggested execution

  • In pollTransactionStatusOnce (internal/soroban/rpc.go) and WaitForConfirmation (internal/soroban/tx.go), replace the manual deadline/time.Now().After(deadline) check with a derived ctx, cancel := context.WithTimeout(ctx, timeout); defer cancel(), and add a case <-ctx.Done(): return nil, timeoutOrCtxErr branch that distinguishes "the derived timeout fired" from "the caller's original context was cancelled" if that distinction matters to callers (check current callers' error-handling expectations before deciding).
  • Add internal/soroban/rpc_test.go (does not exist today) with a test asserting that a timeout shorter than the 2-second poll interval (e.g. 300 * time.Millisecond) causes pollTransactionStatusOnce/PollTransactionStatus to return promptly (within a small margin of timeout, not ~2 seconds later).
  • Add the equivalent short-timeout test for WaitForConfirmation if internal/soroban/tx_test.go exists or is being added as part of related work.

✅ Acceptance criteria

  • A timeout shorter than the 2-second poll interval causes a prompt timeout error, not one delayed until the next ticker tick.
  • Existing longer-timeout polling behavior (roughly one poll every 2 seconds) is unchanged.
  • New tests cover the short-timeout case for at least pollTransactionStatusOnce.

🔒 Security notes

No new attack surface; this is a correctness fix for timeout handling that could otherwise cause callers (e.g. request handlers with their own upstream deadlines) to block longer than intended waiting on a slow/unresponsive Soroban RPC or Horizon endpoint.

📋 Guidelines

  • Minimum 95% test coverage
  • Clear documentation
  • Timeframe: 96 hours

Metadata

Metadata

Assignees

Labels

GrantFox OSSGrantFox open-source programMaybe RewardedGrantFox: potentially rewarded contributionOfficial Campaign | FWC26GrantFox official campaign issuebackendBackend / API workbugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions