Skip to content

improve zero output order pairs handling - #461

Merged
rouzwelt merged 8 commits into
masterfrom
2026-07-20-improve-performance-zero-out-pairs
Jul 26, 2026
Merged

rouzwelt merged 8 commits into
masterfrom
2026-07-20-improve-performance-zero-out-pairs

Conversation

@rouzwelt

@rouzwelt rouzwelt commented Jul 20, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

Solution

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • New Features
    • Improved order processing by separating zero-output from non-zero-output orders and exporting dedicated reporting/telemetry for zero-output.
    • Expanded round telemetry with additional processed-order count reporting.
    • Updated wallet-operation scheduling to run sweep/conversion based on timer windows.
    • Changed wallet reporting to aggregate transfer and swap outcomes into single structured attributes per run.
  • Bug Fixes
    • Corrected vault-balance recalculation and updated simulation headroom factor used for minimum expected values.
  • Tests
    • Updated unit/E2E assertions to match the new order-grouping shape, telemetry fields, timer-driven wallet ops, and simulator/reporting calculations.

@rouzwelt rouzwelt self-assigned this Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@rouzwelt, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e89612e6-170c-4eea-a70e-9901df49eefb

📥 Commits

Reviewing files that changed from the base of the PR and between 645ae2c and f922290.

📒 Files selected for processing (10)
  • config.env.yaml
  • config.example.yaml
  • src/cli/commands/sweep.ts
  • src/config/yaml.test.ts
  • src/config/yaml.ts
  • src/core/modes/simulator.test.ts
  • src/core/modes/simulator.ts
  • src/core/process/round.test.ts
  • src/core/process/round.ts
  • test/e2e/e2e.test.js

Walkthrough

Changes

The round-processing flow partitions orders by sell-token balance, reports zero-output orders, returns total processed order counts, and exposes refreshed order metadata. Wallet operations now use timed scheduling and aggregated report attributes. Trade simulation uses a revised gas headroom factor, with tests updated for all changes.

Order and round processing

Layer / File(s) Summary
Order partitioning and metadata
src/order/index.ts, src/order/index.test.ts
OrderManager refreshes cached counts and returns nonZeroOutput and zeroOutput arrays after recalculating vault balances.
Round initialization and reporting
src/core/process/round.ts, src/core/index.ts, src/core/process/round.test.ts
Round initialization processes non-zero-output pairs, exports zero-output reports, and propagates totalLength.
CLI scheduling, telemetry, and integration validation
src/cli/index.ts, src/cli/index.test.ts, test/e2e/e2e.test.js
CLI telemetry uses total counts and cached metadata, wallet operations use elapsed timers, and quote setup targets non-zero-output pairs.

Wallet operations and reporting

Layer / File(s) Summary
Aggregated wallet reports
src/wallet/index.ts, src/wallet/index.test.ts
Wallet transfers and conversions are collected into serialized transfers and swaps report attributes, with tests covering success and failure cases.

Trade simulation

Layer / File(s) Summary
Gas headroom calculation
src/core/modes/simulator.ts, src/core/modes/simulator.test.ts
Simulation minimum-expected calculations use a 101.5 gas headroom multiplier, with expected transaction data updated accordingly.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant RainSolver
  participant OrderManager
  participant Telemetry
  CLI->>RainSolver: processNextRound()
  RainSolver->>OrderManager: getNextRoundOrders()
  OrderManager-->>RainSolver: nonZeroOutput, zeroOutput
  RainSolver->>Telemetry: export zero-output report
  RainSolver-->>CLI: totalLength and round results
  CLI->>Telemetry: record round metadata
Loading

Possibly related PRs

Suggested labels: ai:ready

Suggested reviewers: hardyjosh

🚥 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 accurately reflects the main change: handling zero-output order pairs.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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
  • Commit unit tests in branch 2026-07-20-improve-performance-zero-out-pairs

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 (1)
src/order/index.ts (1)

614-640: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Accumulate distinctPairsSet across all orderbooks.

By declaring const distinctPairsSet = new Set<string>(); inside the outer this.ownersMap.forEach loop, the set is overwritten for each orderbook. As a result, totalDistinctPairsCount will incorrectly reflect only the distinct pairs of the last orderbook processed.

To correctly count distinct pairs globally, move the Set declaration outside the outer loop.

🐛 Proposed fix for the accumulation bug
-        let totalDistinctPairsCount = 0;
+        const distinctPairsSet = new Set<string>();
         this.ownersMap.forEach((ownersProfileMap) => {
             let obOwners = 0;
             let obOrders = 0;
             let obPairs = 0;
-            const distinctPairsSet = new Set<string>();
             ownersProfileMap.forEach((ownerProfile) => {
                 obOwners++;
                 obOrders += ownerProfile.orders.size;
                 ownerProfile.orders.forEach((orderProfile) => {
                     obPairs += orderProfile.takeOrders.length;
                     orderProfile.takeOrders.forEach((pair) => {
                         distinctPairsSet.add(`${pair.buyToken}-${pair.sellToken}`);
                     });
                 });
             });
             totalCount += obOrders;
             totalOwnersCount += obOwners;
             totalPairsCount += obPairs;
-            totalDistinctPairsCount = distinctPairsSet.size;
         });
         this.metadata = {
             totalCount,
             totalOwnersCount,
             totalPairsCount,
-            totalDistinctPairsCount,
+            totalDistinctPairsCount: distinctPairsSet.size,
         };
🤖 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 `@src/order/index.ts` around lines 614 - 640, Move the distinctPairsSet
declaration outside the outer this.ownersMap.forEach loop, then continue adding
each order’s token pair to that shared set while processing all orderbooks.
Assign totalDistinctPairsCount from the accumulated set size after the loop so
it represents global distinct pairs rather than only the last orderbook.
🤖 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 `@src/core/process/round.ts`:
- Around line 96-119: Update the zeroOutputReport construction in the round
processing flow to avoid storing the entire zeroOutputs array as one JSON span
attribute. Emit the zero-output order details through span events or bounded
batches, preserving pair, owner, orderHash, and orderbook while keeping each
OpenTelemetry attribute payload within exporter size limits.

In `@src/order/index.ts`:
- Around line 443-455: Rename the getNextRoundOrders result property
noneZeroOutput to nonZeroOutput, updating its return type, initialization, all
downstream callers, and test mocks consistently while preserving behavior.

---

Outside diff comments:
In `@src/order/index.ts`:
- Around line 614-640: Move the distinctPairsSet declaration outside the outer
this.ownersMap.forEach loop, then continue adding each order’s token pair to
that shared set while processing all orderbooks. Assign totalDistinctPairsCount
from the accumulated set size after the loop so it represents global distinct
pairs rather than only the last orderbook.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 710ea9e2-6233-47dd-bba5-436686a1ce9b

📥 Commits

Reviewing files that changed from the base of the PR and between 814485c and d5f2630.

📒 Files selected for processing (8)
  • src/cli/index.test.ts
  • src/cli/index.ts
  • src/core/index.ts
  • src/core/process/round.test.ts
  • src/core/process/round.ts
  • src/order/index.test.ts
  • src/order/index.ts
  • test/e2e/e2e.test.js

Comment thread src/core/process/round.ts
Comment thread src/order/index.ts

@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: 4

Caution

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

⚠️ Outside diff range comments (5)
src/order/index.ts (3)

472-497: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Normalize vault-map lookup keys before partitioning.

addToTokenVaultsMap() stores orderbook, owner, and token keys in lowercase, but this lookup uses raw pair.orderbook, owner, and token addresses. A checksummed address misses the cached balance and falls back to stale pair data, so a zero-output pair can enter nonZeroOutput.

Use .toLowerCase() for all lookup keys and add a regression test with mixed-case addresses.

🤖 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 `@src/order/index.ts` around lines 472 - 497, Normalize the orderbook, owner,
sell-token, and buy-token keys to lowercase in the lookups within the
consumingOrders balance-update loop, matching the keys written by
addToTokenVaultsMap. Add a regression test covering mixed-case addresses and
verify cached balances are used so zero-output pairs are partitioned correctly.

635-641: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Compute distinct pairs across all orderbooks.

distinctPairsSet is recreated for each orderbook and totalDistinctPairsCount is overwritten on every iteration. With multiple orderbooks, telemetry reports only the final orderbook’s count rather than the total distinct pair count.

🤖 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 `@src/order/index.ts` around lines 635 - 641, Update the orderbook aggregation
flow around totalDistinctPairsCount and distinctPairsSet so distinct pair
identities are accumulated across all orderbooks instead of resetting or
overwriting the count per iteration. Ensure the metadata returned by the
surrounding method reports the combined distinct-pair count while preserving the
existing totalCount, totalOwnersCount, and totalPairsCount calculations.

201-202: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Avoid recomputing metadata for every order during initial fetch.

fetch() calls addOrder() for every order and then recalculates metadata once more at Line 132. Since getCurrentMetadata() scans all owners, orders, and pairs, initial synchronization becomes quadratic as order volume grows. Defer the per-order refresh during fetch() and retain the final refresh.

🤖 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 `@src/order/index.ts` around lines 201 - 202, Update addOrder() to skip the
getCurrentMetadata() call when invoked during fetch()’s initial synchronization,
while preserving metadata refreshes for other order additions. Keep fetch()’s
existing final getCurrentMetadata() call so metadata is recalculated once after
all orders are loaded.
src/cli/index.ts (1)

354-375: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Advance maintenance timers only after successful operations.

Both timers are moved forward before the awaited work completes. If pending-worker removal, conversion, or any worker sweep fails, the error exits this method and retries are suppressed for one day or five days; a failed worker also prevents later workers from being swept.

Move each timer assignment after its corresponding operations succeed, and add failure-path coverage.

🤖 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 `@src/cli/index.ts` around lines 354 - 375, In the maintenance flow containing
retryPendingRemoveWorkers, convertHoldingsToGas, and the worker sweep loop, move
nextGasConversionTime and nextSweepTime updates until after all corresponding
awaited operations complete successfully. Ensure failures leave the relevant
timer unchanged so the work is retried, while preserving worker iteration
behavior and adding coverage for failed removal, conversion, and worker-sweep
operations.
src/cli/index.test.ts (1)

763-764: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover the new totalLength telemetry contract.

The mocked processNextRound() result omits totalLength, so this test permits ordersMetadata.roundProcessedOrderPairsCount to receive undefined. Include a concrete totalLength and assert that attribute explicitly.

Proposed test update
 (mockRainSolver.processNextRound as Mock).mockResolvedValue({
     results: mockResults,
     reports: mockReports,
     checkpointReports: mockCheckpointReports,
+    totalLength: 4,
 });
 
+expect(mockRoundSpan.setAttribute).toHaveBeenCalledWith(
+    "ordersMetadata.roundProcessedOrderPairsCount",
+    4,
+);
🤖 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 `@src/cli/index.test.ts` around lines 763 - 764, Update the mocked
processNextRound() result in the relevant test to include a concrete totalLength
value, then explicitly assert that ordersMetadata.roundProcessedOrderPairsCount
is set to that value alongside the existing telemetry assertion.
🤖 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 `@src/core/process/round.ts`:
- Around line 97-111: Update the zero-output batching loop in
src/core/process/round.ts:97-111 to use a strict less-than condition so empty
batches never export order_zero_output spans. Update
src/core/process/round.test.ts:436-447 to remove the empty-report expectation
and add coverage confirming a non-empty zero-output batch is exported.

In `@src/wallet/index.test.ts`:
- Around line 361-371: The wallet report tests currently validate transfers and
swaps with substring checks, which can accept malformed or mismatched entries.
Update the relevant assertions in the report tests to parse the serialized
transfers/swaps data and structurally assert arrayContaining objects keyed by
token and type, covering skipped entries and native-gas cases while preserving
the expected transaction, status, and amount fields.

In `@src/wallet/index.ts`:
- Around line 350-370: Add an explicit native-gas discriminator to the
`remainingGas` record before it is pushed into `transfers`, using the
established identity value such as `type: "gas"`. Preserve the existing transfer
status, amount, and transaction fields and ensure the discriminator is present
for both success and failure paths.
- Around line 420-422: Update the skip-sweep branch in the wallet swap flow to
push the modified swap record into the swaps report before continuing,
preserving status = "skipped". Add a regression test covering a token in
skipSweep and assert that the exported swap report includes that skipped record.

---

Outside diff comments:
In `@src/cli/index.test.ts`:
- Around line 763-764: Update the mocked processNextRound() result in the
relevant test to include a concrete totalLength value, then explicitly assert
that ordersMetadata.roundProcessedOrderPairsCount is set to that value alongside
the existing telemetry assertion.

In `@src/cli/index.ts`:
- Around line 354-375: In the maintenance flow containing
retryPendingRemoveWorkers, convertHoldingsToGas, and the worker sweep loop, move
nextGasConversionTime and nextSweepTime updates until after all corresponding
awaited operations complete successfully. Ensure failures leave the relevant
timer unchanged so the work is retried, while preserving worker iteration
behavior and adding coverage for failed removal, conversion, and worker-sweep
operations.

In `@src/order/index.ts`:
- Around line 472-497: Normalize the orderbook, owner, sell-token, and buy-token
keys to lowercase in the lookups within the consumingOrders balance-update loop,
matching the keys written by addToTokenVaultsMap. Add a regression test covering
mixed-case addresses and verify cached balances are used so zero-output pairs
are partitioned correctly.
- Around line 635-641: Update the orderbook aggregation flow around
totalDistinctPairsCount and distinctPairsSet so distinct pair identities are
accumulated across all orderbooks instead of resetting or overwriting the count
per iteration. Ensure the metadata returned by the surrounding method reports
the combined distinct-pair count while preserving the existing totalCount,
totalOwnersCount, and totalPairsCount calculations.
- Around line 201-202: Update addOrder() to skip the getCurrentMetadata() call
when invoked during fetch()’s initial synchronization, while preserving metadata
refreshes for other order additions. Keep fetch()’s existing final
getCurrentMetadata() call so metadata is recalculated once after all orders are
loaded.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6115a4c6-bc44-47ec-b3cb-d7098fdcab1a

📥 Commits

Reviewing files that changed from the base of the PR and between d5f2630 and 7d6deb5.

📒 Files selected for processing (10)
  • src/cli/index.test.ts
  • src/cli/index.ts
  • src/core/modes/simulator.test.ts
  • src/core/modes/simulator.ts
  • src/core/process/round.test.ts
  • src/core/process/round.ts
  • src/order/index.test.ts
  • src/order/index.ts
  • src/wallet/index.test.ts
  • src/wallet/index.ts

Comment thread src/core/process/round.ts Outdated
Comment thread src/wallet/index.test.ts
Comment thread src/wallet/index.ts
Comment thread src/wallet/index.ts
@rouzwelt
rouzwelt merged commit 825c393 into master Jul 26, 2026
12 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base_ref=""
for candidate in origin/master master; do
  if git rev-parse --verify --quiet "$candidate" >/dev/null; then
    base_ref="$candidate"
    break
  fi
done

if [ -z "$base_ref" ]; then
  echo "Unable to locate master base ref." >&2
  exit 1
fi

merge_base="$(git merge-base "$base_ref" HEAD)"
printf 'merge_base=%s\n' "$merge_base"
git diff --stat "$merge_base" HEAD
printf '\n--- numstat ---\n'
git diff --numstat "$merge_base" HEAD
printf '\n--- files ---\n'
git diff --name-only "$merge_base" HEAD

Length of output: 764


SIZE=M

This branch was successfully deployed

1 active deployment
preview — f9222903 Deployed Jul 26, 2026 by github-actions[bot]
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