Skip to content

Enforce per-block work budgets across all ABCI queue processors #225

Description

@nullpointer0x00

Summary

The vault module runs recurring queue work in BeginBlocker/EndBlocker (keeper/abci.go:18-40), but most processors do not enforce a fixed per-block work budget. Block execution time therefore scales with the number of queued entries rather than a bounded per-block limit, so a large backlog of due vault timeouts, verification entries, or pending swap-outs can stretch block time and delay normal vault processing.

Details

1. Three processors walk their entire due set each block (no cap). Each processed vault can trigger reconciliation work (interest transfer, AUM fee collection, NAV publication), so cost is O(queue) per block:

  • handleVaultInterestTimeouts (keeper/reconcile.go) — PayoutTimeoutQueue.WalkDue over all due entries.
  • handleVaultFeeTimeouts (keeper/reconcile.go) — FeeTimeoutQueue.WalkDue over all due entries.
  • handleReconciledVaults (keeper/reconcile.go) — PayoutVerificationSet.Walk(ctx, nil, …) over the whole set.

2. The swap-out batch limit bounds processed jobs, not visited entries. In processPendingSwapOuts (keeper/payout.go:21-46), paused-vault entries return before the counter check, so they are skipped without counting against MaxSwapOutBatchSize:

vault, ok := k.tryGetVault(ctx, vaultAddr)
if ok && vault.Paused {
    return false, nil          // skipped, but walk continues and this is not counted
}
if processed == batchSize {
    return true, nil
}
processed++

A queue front-loaded with paused entries is walked in full every block even though at most batchSize jobs are processed.

3. Paused entries can camp at the front of the swap-out queue. Expedited swap-outs are reinserted at timestamp zero (queue/pending_swap_out.go:129-146). Combined with (2), paused expedited entries can sit permanently at the front, and the iterator re-visits them every block before reaching processable jobs, starving active withdrawals behind them.

Proposed change

  • Apply an explicit per-block visit limit to handleVaultInterestTimeouts, handleVaultFeeTimeouts, and handleReconciledVaults; process at most a configured number of entries per block and leave the remainder queued for subsequent blocks.
  • In processPendingSwapOuts, count visited entries (including skipped paused ones) against the iteration budget, not only successfully processed jobs.
  • Avoid paused entries permanently occupying the front of the swap-out queue, e.g. by separately indexing, refunding, or canceling pending swap-outs for paused vaults so they are not re-walked every block.

Relates to #124 (governance-parameterize the swap-out batch size) and supersedes the temporary-cap note in #75.

Tasks

  • Add a per-block visit/process budget to handleVaultInterestTimeouts, handleVaultFeeTimeouts, and handleReconciledVaults, leaving the remainder queued.
  • Fix processPendingSwapOuts to count visited (including paused) entries against the budget.
  • Prevent paused / timestamp-zero swap-out entries from permanently occupying the front of the queue.
  • Add tests: a backlog larger than the budget is processed across multiple blocks; a paused-heavy front does not block processable jobs or exceed the per-block visit budget.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions