Skip to content

fix: cap the amount of excess providers per block#480

Closed
pablocampogo wants to merge 1 commit into
mainfrom
feat/dex-lp-provider-cap
Closed

fix: cap the amount of excess providers per block#480
pablocampogo wants to merge 1 commit into
mainfrom
feat/dex-lp-provider-cap

Conversation

@pablocampogo

Copy link
Copy Markdown
Collaborator

fix(dex): drain over-cap liquidity pools across multiple blocks

Problem

PR #478 lowered MaxLiquidityProviders from 50,000 to 5,000 and added a one-time
migrateLegacyLPs step to prune pools that were already over the new limit. That migration
evicted the entire excess in a single block.

A nested chain whose pool had reached the old 50,000 limit therefore had to evict 45,000
providers (each a state write + withdraw event, over a fully (de)serialized 50k-entry pool)
in one ApplyBlock. That pushed block application to ~4.1s, which exceeds the per-phase
consensus budgets (ProposeTimeoutMS = 2500ms, ProposeVoteTimeoutMS = 4000ms): the block
could never be applied-and-voted within a round, so it was re-proposed every round and never
committed — the chain livelocked / halted.

A sibling chain that had only ~23,000 providers pruned fine, because evicting ~18,000 fit
inside the round budget. The failure is purely a function of how much eviction work is packed
into one block — the migration path had no per-block bound (unlike order settlement, which is
already capped by MaxOrdersSettledPerBlock).

Change

Bound the legacy LP drain to a fixed number of evictions per block so applying the block
always stays within the consensus round budget. An over-cap pool now drains toward the limit
across multiple blocks instead of in one oversized (unappliable) block.

  • lib/certificate.go — add MaxLPEvictionsPerBlock = 5_000, mirroring the existing
    MaxOrdersSettledPerBlock rationale (cap per-block work to the consensus round budget).
  • fsm/dex.go — in migrateLegacyLPs, cap excess at MaxLPEvictionsPerBlock. The
    existing deterministic lowest-first ranking and eviction logic are unchanged, so each pass
    removes the next-lowest 5,000 providers; the dead address is never evicted. Updated the
    function doc since the drain is no longer one-shot.
  • fsm/dex_test.go — updated TestHandleBatchDepositMigratesPoolBeforeDeposits
    (previously asserted a single call drained 50k→5k) to assert the new behavior: one batch
    prunes exactly MaxLPEvictionsPerBlock, and repeated batches converge to the 5k cap without
    ever evicting the dead address.

Effect

  • A stuck 50k pool drains 50k → 45k → … → 5k over 9 passes. Each pass applies in ~0.5s,
    comfortably inside the propose (2.5s) / propose-vote (4s) windows, so blocks commit and the
    chain makes progress.
  • 9 passes converge well within the 60-block liveness-fallback window, so the secondary
    failure — DexBatch.CheckBasic rejecting a RootDexBatch carrying >5,000 PoolPoints — is
    never reached during the drain.
  • Pools at or below the cap are unaffected (excess <= 0, migration is a no-op).

Notes / follow-ups

  • Consensus-breaking: all validators on the affected chain(s) must run this build; roll it
    out coordinated (same as the feat(dex): cap LP providers with deterministic replacement #478 deploy).
  • The drain is still triggered by DEX deposit-batch processing (the existing
    handleBatchDeposit path), i.e. it advances on blocks that process a liquidity deposit, not
    unconditionally every block. On a live DEX chain deposits flow continuously so it converges,
    but a possible follow-up is to move the drain into an automatic begin_block step so it runs
    independent of deposit flow (and to relax the CheckBasic PoolPoints bound for pools large
    enough that draining would exceed the ~60-block liveness window — not a concern at 50k).

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.

1 participant