Skip to content

feat: add order-take rate limit to prevent spam#828

Open
Matobi98 wants to merge 8 commits into
lnp2pBot:mainfrom
Matobi98:feat/order-take-rate-limit
Open

feat: add order-take rate limit to prevent spam#828
Matobi98 wants to merge 8 commits into
lnp2pBot:mainfrom
Matobi98:feat/order-take-rate-limit

Conversation

@Matobi98

@Matobi98 Matobi98 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Add a rate limit on order taking to prevent spam: users who exceed MAX_ORDERS_TAKE consecutive takes without completing an order are temporarily blocked until the cooldown expires or they complete a trade.

Summary

  • Add MAX_ORDERS_TAKE and ORDER_TAKE_COOLDOWN_HOURS env vars to control the rate limit
  • Track take_order_count and take_order_cooldown_until on the User model
  • Block users from taking new orders once the counter hits the limit, notifying them with the remaining cooldown time
  • Reset the counter and cancel the cooldown when the user completes any order (as maker or taker)

Changes

  • models/user.ts — new fields: take_order_count, take_order_cooldown_until
  • bot/modules/orders/takeOrder.ts — rate limit check in takebuy and takesell
  • util/index.ts — reset counter on order completion (SUCCESS)
  • bot/messages.ts — new orderTakeRateLimitMessage with remaining hours
  • locales/order_take_rate_limit key added in all 10 languages
  • .env-sample — documented new env vars with defaults (MAX_ORDERS_TAKE=10, ORDER_TAKE_COOLDOWN_HOURS=24)

Summary by CodeRabbit

  • New Features
    • Added per-user order-taking rate limiting with configurable maximum accepted orders and a cooldown after hitting the limit.
    • When the limit is reached, users receive localized Telegram notifications with the required wait time (or a prompt to complete an active order to reset).
  • Bug Fixes
    • Reputation updates now automatically reset take counters and cooldowns when no recent successful orders are found.
  • Chores
    • Added new environment variables for rate-limit configuration.
    • Added the new localized message across all supported languages.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds per-user order-taking rate limiting with persisted counters, configurable cooldowns, atomic reservation and rollback, localized rejection messages, and state reset during reputation processing.

Changes

Order-take rate limiting

Layer / File(s) Summary
User model and rate-limit configuration
models/user.ts, .env-sample
Adds persisted take-order counters and cooldown timestamps, plus environment variables for the maximum count and cooldown duration.
Atomic reservation and localized notification
bot/modules/orders/takeOrder.ts, bot/messages.ts, locales/*.yaml
takebuy and takesell reserve slots before saving, release reservations after save failures, and notify blocked users with localized cooldown messages.
Rate-limit state reset
util/index.ts
Clears both users’ counters and cooldown timestamps in the relevant reputation-processing branch.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • lnp2pBot/bot#824: Shares the mutex-controlled takebuy and takesell flow modified by this PR.
  • lnp2pBot/bot#853: Also changes pre-save gating in the order-taking flows.

Suggested reviewers: Luquitasjeffrey, grunch, mostronatorcoder, ermeme

Poem

🐰 I saved each order, one by one,
Then cooled my hops beneath the sun.
In many tongues the message sings,
While finished trades reset the springs.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an order-take rate limit to prevent spam.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
bot/modules/orders/takeOrder.ts (2)

86-97: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persisting the user counter before order save can over-penalize users.

At Line 86/147, incrementTakeOrderCount(user) is saved before Line 96/153 order.save(). If order persistence fails, the user still gets count/cooldown updates for a take that never completed. This should be one atomic unit (or at least update the counter only after order save succeeds).

Also applies to: 147-153, 194-195

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/orders/takeOrder.ts` around lines 86 - 97, The
incrementTakeOrderCount(user) call is executed before persisting the order,
which will penalize the user if order.save() fails; move the
incrementTakeOrderCount(user) to after a successful await order.save() (or wrap
both operations in a DB transaction if supported) so the counter/cooldown is
only updated once order.status, order.seller_id, taken_at and order.random_image
have been persisted; adjust the same pattern wherever incrementTakeOrderCount
appears (lines referencing incrementTakeOrderCount, order.save,
generateRandomImage, and order.status) to ensure counter updates occur post-save
or inside the same atomic transaction.

62-63: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Per-order mutex does not protect per-user counter updates.

At Line 62 and Line 114, locking is keyed by orderId, so the same user can take different orders concurrently and race on take_order_count via Line 194 user.save(). Use an atomic DB update ($inc + conditional cooldown update) or add a per-user lock to avoid lost updates.

Also applies to: 114-115, 184-195

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/orders/takeOrder.ts` around lines 62 - 63, The per-order mutex
(PerOrderIdMutex.instance.runExclusive keyed by orderId) does not prevent
concurrent updates to the same user's take_order_count because different orders
for the same user can run concurrently; replace or augment this by performing an
atomic database update (use a single update with $inc on take_order_count plus
conditional update of the cooldown fields via a conditional operator/aggregation
pipeline) or acquire a per-user lock (e.g., PerUserIdMutex.instance.runExclusive
keyed by user.id) around the critical section that reads/modifies user and calls
user.save(); locate uses of PerOrderIdMutex.instance.runExclusive, the
user.save() call and the take_order_count update and change them to either an
atomic DB operation or wrap the user mutation in a per-user mutex to prevent
lost updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bot/modules/orders/takeOrder.ts`:
- Around line 170-182: The cooldown check is leaving stale state in
user.take_order_count when user.take_order_cooldown_until is in the past; update
the logic in the function in takeOrder.ts that checks
user.take_order_cooldown_until to first detect an expired timestamp and
reset/clear user.take_order_count and user.take_order_cooldown_until before
proceeding, then persist/update the user as needed; continue to call
messages.orderTakeRateLimitMessage(ctx, user, user.take_order_cooldown_until)
only when the cooldown is still in the future. Also apply the same
expired-timestamp reset to the similar block referenced around the 188–193
region.
- Around line 185-186: The env parsing for rate-limit config (maxOrdersTake and
cooldownHours in takeOrder.ts) can yield NaN or non-positive values and disable
cooldown logic; after parsing parseInt(process.env.MAX_ORDERS_TAKE || '10') and
parseInt(process.env.ORDER_TAKE_COOLDOWN_HOURS || '24'), validate each parsed
value (maxOrdersTake and cooldownHours) with Number.isFinite/Number.isInteger
and ensure they are > 0, otherwise reset to safe defaults (e.g., 10 for
maxOrdersTake and 24 for cooldownHours) and optionally clamp to sensible bounds;
update any usages that rely on these variables (e.g., rate-limit checks like
count >= cooldownHours) to use the validated values.

---

Outside diff comments:
In `@bot/modules/orders/takeOrder.ts`:
- Around line 86-97: The incrementTakeOrderCount(user) call is executed before
persisting the order, which will penalize the user if order.save() fails; move
the incrementTakeOrderCount(user) to after a successful await order.save() (or
wrap both operations in a DB transaction if supported) so the counter/cooldown
is only updated once order.status, order.seller_id, taken_at and
order.random_image have been persisted; adjust the same pattern wherever
incrementTakeOrderCount appears (lines referencing incrementTakeOrderCount,
order.save, generateRandomImage, and order.status) to ensure counter updates
occur post-save or inside the same atomic transaction.
- Around line 62-63: The per-order mutex (PerOrderIdMutex.instance.runExclusive
keyed by orderId) does not prevent concurrent updates to the same user's
take_order_count because different orders for the same user can run
concurrently; replace or augment this by performing an atomic database update
(use a single update with $inc on take_order_count plus conditional update of
the cooldown fields via a conditional operator/aggregation pipeline) or acquire
a per-user lock (e.g., PerUserIdMutex.instance.runExclusive keyed by user.id)
around the critical section that reads/modifies user and calls user.save();
locate uses of PerOrderIdMutex.instance.runExclusive, the user.save() call and
the take_order_count update and change them to either an atomic DB operation or
wrap the user mutation in a per-user mutex to prevent lost updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 870f3837-70a7-4381-a84d-d62b7257d716

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2beea and bcb433d.

📒 Files selected for processing (15)
  • .env-sample
  • bot/messages.ts
  • bot/modules/orders/takeOrder.ts
  • locales/de.yaml
  • locales/en.yaml
  • locales/es.yaml
  • locales/fa.yaml
  • locales/fr.yaml
  • locales/it.yaml
  • locales/ko.yaml
  • locales/pt.yaml
  • locales/ru.yaml
  • locales/uk.yaml
  • models/user.ts
  • util/index.ts

Comment thread bot/modules/orders/takeOrder.ts Outdated
Comment thread bot/modules/orders/takeOrder.ts Outdated

@Luquitasjeffrey Luquitasjeffrey left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please solve your conflicts with main to proceed with the review

…e-limit

# Conflicts:
#	bot/messages.ts
#	bot/modules/orders/takeOrder.ts
#	models/user.ts
#	package-lock.json

@ermeme ermeme Bot left a comment

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.

Code Review Summary

Verdict: Request changes

The stale cooldown/env parsing issues are fixed on the current head, but one blocker remains: the take counter is still updated from stale in-memory state, and it is written before the order itself is persisted. That leaves the spam cap bypassable under concurrent takes from the same user, and it can also penalize a user for a take that never completed because order.save() failed.

Comment thread bot/modules/orders/takeOrder.ts Outdated
Comment thread bot/modules/orders/takeOrder.ts Outdated

@ermeme ermeme Bot left a comment

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.

Code Review Summary

Verdict: Request changes

The save-before-increment issue is fixed on the current head, but one blocker remains: the take limit is still enforced only after a stale read. checkTakeRateLimit() reads the user document before the atomic $inc, and there is no per-user lock across different orders. Two or more concurrent takes by the same user can all pass the check and push the counter past MAX_ORDERS_TAKE, which defeats the anti-spam protection this PR is meant to add.

There is also still a crash window between incrementing take_order_count and writing take_order_cooldown_until, so a failed second write can leave the cap unenforced until the next successful take.

Please gate the counter with an atomic conditional update or a per-user mutex before any order side effects.

Comment thread bot/modules/orders/takeOrder.ts Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bot/modules/orders/takeOrder.ts`:
- Around line 277-291: The updatedUser === null branch in the order-taking flow
can return without notifying the user when the follow-up User.findById read
lacks take_order_cooldown_until. Add a single retry of the cooldown reservation
or emit a fallback blocked message when current is missing or has no cooldown,
ensuring the branch always provides feedback before returning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 30cf41d9-f361-476a-8bfa-8c6b5fe30113

📥 Commits

Reviewing files that changed from the base of the PR and between 132de43 and dccf554.

📒 Files selected for processing (1)
  • bot/modules/orders/takeOrder.ts

Comment thread bot/modules/orders/takeOrder.ts
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