Skip to content

fix(market): bound bids per order with a monotonic bids-ever-created counter#2071

Open
renezander030 wants to merge 2 commits into
akash-network:mainfrom
renezander030:fix/market-bid-cap-monotonic-counter
Open

fix(market): bound bids per order with a monotonic bids-ever-created counter#2071
renezander030 wants to merge 2 commits into
akash-network:mainfrom
renezander030:fix/market-bid-cap-monotonic-counter

Conversation

@renezander030

Copy link
Copy Markdown

Follow-up to #2068, implementing the design discussed in #2068 (comment) (ack: #2068 (comment)).

Note: stacked on #2068 — the first commit here is that PR's commit; the diff collapses to the second commit once #2068 merges.

Problem

CreateBid enforced OrderMaxBids by scanning the order's open, active and closed bids on every call:

  • Counting closed bids was the only backpressure on an order, so the count doubled as a lifetime cap. Excluding them (as proposed in the fix(market): enforce OrderMaxBids with >= instead of > #2068 review thread) would have opened a churn loop: a provider cycles CreateBid > CloseBid forever, never hits the cap, and every cycle leaves a BidClosed record behind.
  • The scan missed BidLost entirely, so lost bids escaped the cap.
  • It cost an iteration over three state indexes per CreateBid.

Change

  • New per-order monotonic counter (bid_counts, prefix 0x12 0x06): bumped on CreateBid, never decremented when a bid closes or loses. The cap check reads it in O(1), and total bid records per order stay bounded by OrderMaxBids regardless of churn — closed and lost bids included.
  • Lazy seeding for orders that predate the counter: when no counter record exists, the count is derived from a one-time scan of all stored bids (all four states). The cap therefore carries over an upgrade or genesis import unchanged, and no store migration is needed. The counter is read (and seeded) before the new bid is stored so the seed never double-counts the bid being created.
  • BidCountForOrder now returns only live (open or active) bids, for consumers that care about current competition on an order rather than the lifetime cap.

Tests

  • TestCreateBidCapSurvivesBidChurn — closing a bid drops the live count to zero but does not free cap capacity; a further bid is rejected.
  • TestBidsEverCreatedLazySeedsFromStore — bids seeded directly in the store (genesis-style, no counter record) still count toward the cap, including closed and lost ones.
  • TestCreateBidExceedsOrderMaxBids from fix(market): enforce OrderMaxBids with >= instead of > #2068 passes unchanged, now exercising the counter path.

go test ./x/market/... -count=1 passes; full repo builds.

🤖 Generated with Claude Code

renezander030 and others added 2 commits June 9, 2026 12:55
CreateBid compared the existing bid count against OrderMaxBids using `>`,
which let one extra bid through and allowed OrderMaxBids+1 bids per order.
BidCountForOrder counts Open, Active and Closed bids, so the count reaches
OrderMaxBids exactly at the cap and the check must reject at that point.

Switch the comparison to `>=` and add a regression test that seeds an
order to its cap and asserts a further bid is rejected with
"too many existing bids".

Closes akash-network/support#413

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…counter

CreateBid enforced OrderMaxBids by scanning the order's open, active and
closed bids on every call. Counting closed bids was the only backpressure
on an order, so the count doubled as a lifetime cap; the scan also missed
lost bids entirely and cost O(n) across three state indexes per bid.

Replace the scan with a per-order monotonic counter (bids-ever-created),
bumped on CreateBid and never decremented when a bid closes or loses.
The cap check reads the counter in O(1), and total bid records per order
stay bounded by OrderMaxBids regardless of create/close churn, which
closes the state-inflation loop where a provider cycles
CreateBid > CloseBid forever without ever hitting the cap.

Orders that predate the counter are seeded lazily from a scan of all
stored bids (including lost and closed), so the cap carries over an
upgrade or genesis import unchanged and no store migration is needed.

BidCountForOrder now returns only live (open or active) bids for
consumers that care about current competition on an order.

Follow-up to akash-network#2068, as discussed in
akash-network#2068 (comment)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@renezander030 renezander030 requested a review from a team as a code owner June 11, 2026 08:52
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fc4a65aa-409c-423c-b363-5da64cd2374f

📥 Commits

Reviewing files that changed from the base of the PR and between 151b989 and 020e849.

📒 Files selected for processing (4)
  • x/market/handler/handler_test.go
  • x/market/handler/server.go
  • x/market/keeper/keeper.go
  • x/market/keeper/keys/key.go

Walkthrough

This PR replaces bid capacity enforcement based on live bid count with a monotonic "bids ever created" counter, preventing bid-create/close loops from exhausting state. The keeper adds persistent counter storage, exports a new BidsEverCreatedForOrder method with lazy seeding, changes BidCountForOrder semantics to live bids only, and the handler validates against the ever-created cap.

Changes

Bid Capacity Counter Implementation

Layer / File(s) Summary
Storage Schema Setup
x/market/keeper/keys/key.go, x/market/keeper/keeper.go
BidCountPrefix key added; Keeper struct extended with bidCounts collection; schema registration and keeper field wiring completed.
Keeper API Contract & Imports
x/market/keeper/keeper.go
IKeeper interface extended with BidsEverCreatedForOrder method; errors package imported for collection error handling.
Keeper Counting Implementation
x/market/keeper/keeper.go
BidCountForOrder redefined to count only live bids; BidsEverCreatedForOrder added with lazy seeding that scans all bid states on first access when counter is absent.
Counter Increment & Handler Validation
x/market/keeper/keeper.go, x/market/handler/server.go
CreateBid increments monotonic counter before persisting bid; handler CreateBid validates that BidsEverCreatedForOrder does not exceed OrderMaxBids.
Regression Tests
x/market/handler/handler_test.go
TestCreateBidExceedsOrderMaxBids verifies rejection when pre-seeded count hits limit; TestCreateBidCapSurvivesBidChurn confirms cap persists after bid closure; TestBidsEverCreatedLazySeedsFromStore validates lazy initialization from stored bids.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A counter keeps track of every bid,
Never freeing space when bids are hid,
Close them or lose them, the cap stays tight,
State stays lean, no looping blight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: introducing a monotonic bids-ever-created counter to bound bids per order.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the problem, solution, tests, and implementation details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Tools execution failed with the following error:

Failed to run tools: Ping-pong health check failed


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant