Skip to content

feat(indexer): push result cap and ordering into KV block search (PLT-748)#3689

Open
amir-deris wants to merge 1 commit into
mainfrom
amir/plt-748-bound-kv-indexer
Open

feat(indexer): push result cap and ordering into KV block search (PLT-748)#3689
amir-deris wants to merge 1 commit into
mainfrom
amir/plt-748-bound-kv-indexer

Conversation

@amir-deris

@amir-deris amir-deris commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

BlockSearch/TxSearch previously fetched the entire match set from the KV
indexer, sorted it, and only then applied MaxTxSearchResults at the RPC layer.
For broad queries this materializes and sorts far more heights than the caller
will ever see. This PR pushes the result cap and ordering down into the
indexer
so a broad block query is bounded at the scan path.

What changed

  • New indexer.SearchOptions{Limit, OrderDesc} threaded through the
    TxIndexer / BlockIndexer interfaces, the EventSink interface, the KV
    sink, and the null/mock implementations.
  • KV block indexer (block/kv) — bounded search:
    • Fast path (planBounded / searchBounded): when every condition is an
      equality (point-probeable) or a block.height range (evaluable from the
      candidate height), drive a single height-ordered scan in order_by order,
      point-probe the remaining conditions per candidate, and stop at Limit.
      Memory is bounded by results kept, not by total match cardinality.
    • Fallback path (intersect + collectBounded): queries with
      CONTAINS/MATCHES/EXISTS or non-height ranges can't be point-probed, so
      the intersection is materialized as before, then ordered and capped.
  • RPC BlockSearch: validates order_by up front, passes it and the cap
    into the indexer, and keeps the post-sort cap as a safety net for sinks that
    ignore the limit.
  • RPC TxSearch: passes SearchOptions through now, but the tx scan path is
    not yet bounded — marked with TODO(PLT-748); behavior is unchanged for tx.

Tests

  • TestBlockIndexerBounded covers the fast path (equality driver, multi-equality
    probe, equality + height range, pure height-range driver) and the fallback
    path (CONTAINS), across asc/desc and bounded/unbounded limits.
  • Existing indexer/sink/rpc tests updated for the new SearchOptions argument.

Follow-up

  • Bound the KV tx scan path the same way (TODO(PLT-748) in tx.go).

PLT-748

@cursor

cursor Bot commented Jul 1, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches public indexer/RPC search signatures and block search execution paths where incorrect bounded logic could change which heights are returned; tx search behavior is unchanged until PLT-748.

Overview
Introduces SearchOptions (Limit, OrderDesc) on indexer Search / SearchBlockEvents / SearchTxEvents, so RPC can pass MaxTxSearchResults and order_by into the index layer instead of only capping after a full fetch.

Block search is the main behavioral change: the KV block indexer adds a bounded fast path that scans height-ordered keys, point-probes other equality/block.height conditions, and stops at the limit; complex queries still intersect then collectBounded. BlockSearch validates order_by, calls the sink with options, and keeps sort plus a safety cap for sinks that ignore limits.

Tx search passes the same options through the API, but the KV tx indexer still ignores them (noted as PLT-748); sorting and capping remain in RPC. Mocks, null/psql sinks, and tests were updated for the new signatures; TestBlockIndexerBounded covers ordered, limited block queries.

Reviewed by Cursor Bugbot for commit 0f29829. Bugbot is set up for automated code reviews on this repo. Configure here.

@amir-deris amir-deris changed the title Pushed limit/order block search into indexer, added tests feat(indexer): push result cap and ordering into KV block search (PLT-748) Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJul 1, 2026, 5:11 PM

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 67.24138% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.34%. Comparing base (93667a4) to head (0f29829).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...i-tendermint/internal/state/indexer/block/kv/kv.go 72.64% 15 Missing and 14 partials ⚠️
...dermint/internal/state/indexer/mocks/event_sink.go 25.00% 6 Missing and 6 partials ⚠️
...tendermint/internal/state/indexer/block/kv/util.go 68.18% 5 Missing and 2 partials ⚠️
sei-tendermint/internal/rpc/core/blocks.go 66.66% 4 Missing and 1 partial ⚠️
...endermint/internal/state/indexer/sink/psql/psql.go 0.00% 2 Missing ⚠️
...ndermint/internal/state/indexer/block/null/null.go 0.00% 1 Missing ⚠️
...-tendermint/internal/state/indexer/tx/null/null.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3689      +/-   ##
==========================================
- Coverage   59.26%   58.34%   -0.93%     
==========================================
  Files        2272     2185      -87     
  Lines      188030   178390    -9640     
==========================================
- Hits       111442   104080    -7362     
+ Misses      66568    65077    -1491     
+ Partials    10020     9233     -787     
Flag Coverage Δ
sei-chain-pr 43.04% <67.24%> (?)
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-tendermint/internal/rpc/core/tx.go 69.73% <100.00%> (+1.24%) ⬆️
sei-tendermint/internal/state/indexer/indexer.go 100.00% <ø> (ø)
...ei-tendermint/internal/state/indexer/sink/kv/kv.go 100.00% <100.00%> (ø)
...endermint/internal/state/indexer/sink/null/null.go 100.00% <100.00%> (ø)
sei-tendermint/internal/state/indexer/tx/kv/kv.go 66.21% <100.00%> (ø)
...ndermint/internal/state/indexer/block/null/null.go 0.00% <0.00%> (ø)
...-tendermint/internal/state/indexer/tx/null/null.go 0.00% <0.00%> (ø)
...endermint/internal/state/indexer/sink/psql/psql.go 2.54% <0.00%> (ø)
sei-tendermint/internal/rpc/core/blocks.go 66.09% <66.66%> (+0.42%) ⬆️
...tendermint/internal/state/indexer/block/kv/util.go 80.00% <68.18%> (-4.91%) ⬇️
... and 2 more

... and 87 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A focused, well-documented refactor that pushes the result cap and ordering into the KV block indexer's scan path; the new fast/fallback paths faithfully reproduce the legacy match semantics and are well covered by tests. No correctness or security issues found — only minor non-blocking notes.

Findings: 0 blocking | 5 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Second-opinion passes: Codex reported no material findings; the Cursor review file (cursor-review.md) is empty (no output produced). Noting per review instructions.
  • Test coverage gap: TestBlockIndexerBounded covers equality drivers and a single lower-bound height range (desc), but not (a) a pure block.height-range driver in ascending order, (b) a dual-bounded range (block.height >= x AND block.height <= y), or (c) context-cancellation returning partial results in the bounded path. The logic appears correct for all three; adding cases would lock the behavior in.
  • Minor perf: the pure block.height-range driver scans from the height extremum instead of seeking to the range boundary, so a bounded query like block.height <= K on a tall chain still iterates every height above K (cheap range-reject, no point-probe). This matches the old O(total) cost, so it's not a regression — a future optimization could seek the iterator to the bound.
  • The seen dedup map in searchBounded is effectively defensive: block event keys are unique per (compositeKey, eventValue, height) because the type suffix is always "finalize_block", so a height cannot repeat within a single driver scan today. Fine to keep for robustness; just noting it is not exercised.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

// and ordering down to the scan.
results, err := sink.SearchTxEvents(ctx, q, indexer.SearchOptions{
Limit: env.Config.MaxTxSearchResults,
OrderDesc: req.OrderBy != "asc",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] Nit: OrderDesc: req.OrderBy != "asc" maps any invalid order_by (e.g. "foo") to desc. Harmless today because the tx indexer ignores opts entirely and the switch below (lines 83-100) still validates order_by and returns ErrInvalidRequest. Worth keeping the TODO(PLT-748) note in mind: once the tx scan path honors these opts, order_by should be validated before it's turned into OrderDesc so a bad value can't silently drive the scan.

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