Skip to content

graphql-alt: scanning api bloom filter pipelines#24788

Merged
henryachen merged 21 commits intomainfrom
hc/dvx-1290-scanning-api-write
Jan 30, 2026
Merged

graphql-alt: scanning api bloom filter pipelines#24788
henryachen merged 21 commits intomainfrom
hc/dvx-1290-scanning-api-write

Conversation

@henryachen
Copy link
Copy Markdown
Collaborator

@henryachen henryachen commented Jan 5, 2026

Description

Adds two new indexer pipelines to support transaction scanning queries via bloom filters:

cp_blooms concurrent pipeline: Per-checkpoint bloom filters

  • Indexes function calls, affected objects, senders, and recipients for each checkpoint
  • Folds bloom filter if it is sparse up to a certain min size or density
  • Stored in cp_blooms table with one row per checkpoint

cp_bloom_blocks sequential pipeline: Blocked bloom filters spanning 1000 checkpoints

  • Splits each 256KB bloom into 128 separate smaller 2KB blocks
  • Stored in cp_bloom_blocks table with ~128 rows per cp_block (sparse, only non-zero blocks)
  • ORs bloom blocks on conflicts

Schema changes:

  • cp_blooms: Per-checkpoint bloom filters
  • cp_bloom_blocks: Blocked bloom filters with (cp_block_id, bloom_block_index) primary key

Test plan

cargo nextest run -p sui-indexer-alt
cargo nextest run -p sui-indexer-alt-schema

Stack:

#24788 👈
#24900
#25097


Release notes

Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required.

For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates.

  • Protocol:
  • Nodes (Validators and Full nodes):
  • gRPC:
  • JSON-RPC:
  • GraphQL: add bloom filter pipelines for scanning APIs
  • CLI:
  • Rust SDK:
  • Indexing Framework:

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sui-docs Ready Ready Preview, Comment Jan 30, 2026 0:44am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
multisig-toolkit Ignored Ignored Preview Jan 30, 2026 0:44am
sui-kiosk Ignored Ignored Preview Jan 30, 2026 0:44am

Request Review

@henryachen henryachen force-pushed the hc/dvx-1290-scanning-api-write branch from b1ce241 to 7a0f2d4 Compare January 14, 2026 06:10
@henryachen henryachen temporarily deployed to sui-typescript-aws-kms-test-env January 14, 2026 06:10 — with GitHub Actions Inactive
@henryachen henryachen temporarily deployed to sui-typescript-aws-kms-test-env January 16, 2026 00:22 — with GitHub Actions Inactive
@henryachen henryachen force-pushed the hc/dvx-1290-scanning-api-write branch from 85986bb to bcfadfe Compare January 30, 2026 00:40
@henryachen henryachen temporarily deployed to sui-typescript-aws-kms-test-env January 30, 2026 00:40 — with GitHub Actions Inactive
@henryachen henryachen requested review from amnn and wlmyng January 30, 2026 02:05
@henryachen henryachen merged commit e984ad8 into main Jan 30, 2026
72 of 77 checks passed
@henryachen henryachen deleted the hc/dvx-1290-scanning-api-write branch January 30, 2026 18:28
henryachen added a commit that referenced this pull request Feb 6, 2026
## Description 

Adds two new indexer pipelines to support transaction scanning queries
via bloom filters:

  cp_blooms concurrent pipeline: Per-checkpoint bloom filters
- Indexes function calls, affected objects, senders, and recipients for
each checkpoint
- Folds bloom filter if it is sparse up to a certain min size or density
  - Stored in cp_blooms table with one row per checkpoint

cp_bloom_blocks sequential pipeline: Blocked bloom filters spanning 1000
checkpoints
  - Splits each 256KB bloom into 128 separate smaller 2KB blocks
- Stored in cp_bloom_blocks table with ~128 rows per cp_block (sparse,
only non-zero blocks)
  - ORs bloom blocks on conflicts

  Schema changes:
  - cp_blooms: Per-checkpoint bloom filters
- cp_bloom_blocks: Blocked bloom filters with (cp_block_id,
bloom_block_index) primary key

## Test plan
```
cargo nextest run -p sui-indexer-alt
cargo nextest run -p sui-indexer-alt-schema
```

## Stack:
#24788 👈 
#24900
#25097


---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [x] GraphQL:   add bloom filter pipelines for scanning APIs
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
henryachen added a commit that referenced this pull request Feb 6, 2026
## Description 

Adds two new indexer pipelines to support transaction scanning queries
via bloom filters:

  cp_blooms concurrent pipeline: Per-checkpoint bloom filters
- Indexes function calls, affected objects, senders, and recipients for
each checkpoint
- Folds bloom filter if it is sparse up to a certain min size or density
  - Stored in cp_blooms table with one row per checkpoint

cp_bloom_blocks sequential pipeline: Blocked bloom filters spanning 1000
checkpoints
  - Splits each 256KB bloom into 128 separate smaller 2KB blocks
- Stored in cp_bloom_blocks table with ~128 rows per cp_block (sparse,
only non-zero blocks)
  - ORs bloom blocks on conflicts

  Schema changes:
  - cp_blooms: Per-checkpoint bloom filters
- cp_bloom_blocks: Blocked bloom filters with (cp_block_id,
bloom_block_index) primary key

## Test plan
```
cargo nextest run -p sui-indexer-alt
cargo nextest run -p sui-indexer-alt-schema
```

## Stack:
#24788 👈 
#24900
#25097


---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [x] GraphQL:   add bloom filter pipelines for scanning APIs
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
henryachen added a commit that referenced this pull request Mar 4, 2026
## Description 

- Added scanEvent GraphQL query endpoint for scanning events using bloom
filters
  - Reuse existing scan infrastructure (bloom querying, pagination) from
 Transaction Scanning
- Added e2e tests covering filtering by sender, module, type, and
pagination

## Test plan 

```
cargo nextest run -p sui-indexer-alt-graphql
cargo nextest run -p sui-indexer-alt-graphql -- schema_sdl
cargo nextest run -p sui-indexer-alt-graphql --features staging -- schema_sdl
cargo nextest run -p sui-indexer-alt-e2e-tests -- graphql/
```

## Stack:
#24788 
#24900 
#25097 👈 

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
henryachen added a commit that referenced this pull request Mar 5, 2026
## Description 

- Added scanEvent GraphQL query endpoint for scanning events using bloom
filters
  - Reuse existing scan infrastructure (bloom querying, pagination) from
 Transaction Scanning
- Added e2e tests covering filtering by sender, module, type, and
pagination

## Test plan 

```
cargo nextest run -p sui-indexer-alt-graphql
cargo nextest run -p sui-indexer-alt-graphql -- schema_sdl
cargo nextest run -p sui-indexer-alt-graphql --features staging -- schema_sdl
cargo nextest run -p sui-indexer-alt-e2e-tests -- graphql/
```

## Stack:
#24788 
#24900 
#25097 👈 

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
henryachen added a commit that referenced this pull request Mar 5, 2026
## Description 

Adds a new transactionsScan query that scans checkpoints using bloom
filters to find transactions matching filter criteria.

  Changes

  GraphQL (sui-indexer-alt-graphql)
  - New transactionsScan query endpoint
  - maxScanLimit service config to limit checkpoints scanned per query
  - Transaction filter and scan logic in scan.rs
  
  Schema (sui-indexer-alt-schema)
- `bloom_contains` SQL function for checking probe membership in bloom
filters

  Reader (sui-indexer-alt-reader)
  - `cp_blooms` loader for batch-loading bloom filter data


## Test plan 

```
cargo nextest run -p sui-indexer-alt-graphql graphql_scan_limit_tests
cargo nextest run -p sui-indexer-alt-graphql
cargo nextest run -p sui-indexer-alt
cargo nextest run -p sui-indexer-alt-schema
```

Query Plan:
```
Limit  (cost=1911.69..1358704.33 rows=62 width=8) (actual time=14.454..14.836 rows=62 loops=1)
  Buffers: shared hit=7287
  CTE block_bit_probes
    ->  ProjectSet  (cost=0.00..8.20 rows=1635 width=18) (actual time=0.004..0.431 rows=1635 loops=1)
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
  ->  Nested Loop  (cost=1903.49..1358696.13 rows=62 width=8) (actual time=14.453..14.830 rows=62 loops=1)
        Buffers: shared hit=7287
        ->  Limit  (cost=1903.06..1903.22 rows=62 width=24) (actual time=14.411..14.414 rows=1 loops=1)
              Buffers: shared hit=6976
              CTE block_lookup
                ->  Nested Loop Left Join  (cost=41.30..1723.38 rows=200 width=131) (actual time=0.654..2.899 rows=327 loops=1)
                      Buffers: shared hit=1348
                      ->  HashAggregate  (cost=40.88..42.88 rows=200 width=10) (actual time=0.620..0.682 rows=327 loops=1)
                            Group Key: block_bit_probes_1.cp_block_index, block_bit_probes_1.bloom_idx
                            Batches: 1  Memory Usage: 61kB
                            ->  CTE Scan on block_bit_probes block_bit_probes_1  (cost=0.00..32.70 rows=1635 width=10) (actual time=0.000..0.159 rows=1635 loops=1)
                      ->  Index Scan using cp_bloom_blocks_pkey on cp_bloom_blocks bb  (cost=0.42..8.40 rows=1 width=131) (actual time=0.006..0.006 rows=1 loops=327)
                            Index Cond: ((cp_block_index = block_bit_probes_1.cp_block_index) AND (bloom_block_index = block_bit_probes_1.bloom_idx))
                            Buffers: shared hit=1343
              ->  Sort  (cost=179.69..180.17 rows=192 width=24) (actual time=14.410..14.412 rows=1 loops=1)
                    Sort Key: ((block_bit_probes.cp_block_index * '1000'::bigint))
                    Sort Method: quicksort  Memory: 25kB
                    Buffers: shared hit=6976
                    ->  Hash Right Anti Join  (cost=48.29..173.01 rows=192 width=24) (actual time=14.392..14.400 rows=5 loops=1)
                          Hash Cond: (p.cp_block_index = block_bit_probes.cp_block_index)
                          Buffers: shared hit=6976
                          ->  Hash Join  (cost=7.00..129.78 rows=8 width=8) (actual time=3.224..12.355 rows=1011 loops=1)
                                Hash Cond: ((p.cp_block_index = bl.cp_block_index) AND (p.bloom_idx = bl.bloom_idx))
                                Join Filter: ((bl.bloom_filter IS NULL) OR (p.bit_mask <> get_byte(bl.bloom_filter, (p.byte_pos % length(bl.bloom_filter)))))
                                Rows Removed by Join Filter: 624
                                Buffers: shared hit=6976
                                ->  CTE Scan on block_bit_probes p  (cost=0.00..32.70 rows=1635 width=18) (actual time=0.000..0.211 rows=1635 loops=1)
                                ->  Hash  (cost=4.00..4.00 rows=200 width=42) (actual time=3.142..3.142 rows=327 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 93kB
                                      Buffers: shared hit=1348
                                      ->  CTE Scan on block_lookup bl  (cost=0.00..4.00 rows=200 width=42) (actual time=0.656..3.060 rows=327 loops=1)
                                            Buffers: shared hit=1348
                          ->  Hash  (cost=38.79..38.79 rows=200 width=8) (actual time=1.773..1.774 rows=327 loops=1)
                                Buckets: 1024  Batches: 1  Memory Usage: 21kB
                                ->  HashAggregate  (cost=36.79..38.79 rows=200 width=8) (actual time=1.668..1.717 rows=327 loops=1)
                                      Group Key: block_bit_probes.cp_block_index
                                      Batches: 1  Memory Usage: 61kB
                                      ->  CTE Scan on block_bit_probes  (cost=0.00..32.70 rows=1635 width=8) (actual time=0.007..1.162 rows=1635 loops=1)
        ->  Index Scan using cp_blooms_pkey on cp_blooms cb  (cost=0.43..21883.74 rows=1 width=8) (actual time=0.040..0.408 rows=62 loops=1)
              Index Cond: ((cp_sequence_number >= ((block_bit_probes.cp_block_index * '1000'::bigint))) AND (cp_sequence_number <= ((((block_bit_probes.cp_block_index * '1000'::bigint) + '1000'::bigint) - 1))))
              Filter: (((get_byte(bloom_filter, (208 % length(bloom_filter))) & 8) = 8) AND ((get_byte(bloom_filter, (5988 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8084 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (15059 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8330 % length(bloom_filter))) & 64) = 64) AND ((get_byte(bloom_filter, (10313 % length(bloom_filter))) & 4) = 4))
              Rows Removed by Filter: 318
              Buffers: shared hit=311
Planning:
  Buffers: shared hit=78
Planning Time: 2.181 ms
Execution Time: 15.060 ms
```

## Stack:
#24788 
#24900 👈 
#25097
---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
henryachen added a commit that referenced this pull request Mar 5, 2026
## Description 

Adds a new transactionsScan query that scans checkpoints using bloom
filters to find transactions matching filter criteria.

  Changes

  GraphQL (sui-indexer-alt-graphql)
  - New transactionsScan query endpoint
  - maxScanLimit service config to limit checkpoints scanned per query
  - Transaction filter and scan logic in scan.rs
  
  Schema (sui-indexer-alt-schema)
- `bloom_contains` SQL function for checking probe membership in bloom
filters

  Reader (sui-indexer-alt-reader)
  - `cp_blooms` loader for batch-loading bloom filter data


## Test plan 

```
cargo nextest run -p sui-indexer-alt-graphql graphql_scan_limit_tests
cargo nextest run -p sui-indexer-alt-graphql
cargo nextest run -p sui-indexer-alt
cargo nextest run -p sui-indexer-alt-schema
```

Query Plan:
```
Limit  (cost=1911.69..1358704.33 rows=62 width=8) (actual time=14.454..14.836 rows=62 loops=1)
  Buffers: shared hit=7287
  CTE block_bit_probes
    ->  ProjectSet  (cost=0.00..8.20 rows=1635 width=18) (actual time=0.004..0.431 rows=1635 loops=1)
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
  ->  Nested Loop  (cost=1903.49..1358696.13 rows=62 width=8) (actual time=14.453..14.830 rows=62 loops=1)
        Buffers: shared hit=7287
        ->  Limit  (cost=1903.06..1903.22 rows=62 width=24) (actual time=14.411..14.414 rows=1 loops=1)
              Buffers: shared hit=6976
              CTE block_lookup
                ->  Nested Loop Left Join  (cost=41.30..1723.38 rows=200 width=131) (actual time=0.654..2.899 rows=327 loops=1)
                      Buffers: shared hit=1348
                      ->  HashAggregate  (cost=40.88..42.88 rows=200 width=10) (actual time=0.620..0.682 rows=327 loops=1)
                            Group Key: block_bit_probes_1.cp_block_index, block_bit_probes_1.bloom_idx
                            Batches: 1  Memory Usage: 61kB
                            ->  CTE Scan on block_bit_probes block_bit_probes_1  (cost=0.00..32.70 rows=1635 width=10) (actual time=0.000..0.159 rows=1635 loops=1)
                      ->  Index Scan using cp_bloom_blocks_pkey on cp_bloom_blocks bb  (cost=0.42..8.40 rows=1 width=131) (actual time=0.006..0.006 rows=1 loops=327)
                            Index Cond: ((cp_block_index = block_bit_probes_1.cp_block_index) AND (bloom_block_index = block_bit_probes_1.bloom_idx))
                            Buffers: shared hit=1343
              ->  Sort  (cost=179.69..180.17 rows=192 width=24) (actual time=14.410..14.412 rows=1 loops=1)
                    Sort Key: ((block_bit_probes.cp_block_index * '1000'::bigint))
                    Sort Method: quicksort  Memory: 25kB
                    Buffers: shared hit=6976
                    ->  Hash Right Anti Join  (cost=48.29..173.01 rows=192 width=24) (actual time=14.392..14.400 rows=5 loops=1)
                          Hash Cond: (p.cp_block_index = block_bit_probes.cp_block_index)
                          Buffers: shared hit=6976
                          ->  Hash Join  (cost=7.00..129.78 rows=8 width=8) (actual time=3.224..12.355 rows=1011 loops=1)
                                Hash Cond: ((p.cp_block_index = bl.cp_block_index) AND (p.bloom_idx = bl.bloom_idx))
                                Join Filter: ((bl.bloom_filter IS NULL) OR (p.bit_mask <> get_byte(bl.bloom_filter, (p.byte_pos % length(bl.bloom_filter)))))
                                Rows Removed by Join Filter: 624
                                Buffers: shared hit=6976
                                ->  CTE Scan on block_bit_probes p  (cost=0.00..32.70 rows=1635 width=18) (actual time=0.000..0.211 rows=1635 loops=1)
                                ->  Hash  (cost=4.00..4.00 rows=200 width=42) (actual time=3.142..3.142 rows=327 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 93kB
                                      Buffers: shared hit=1348
                                      ->  CTE Scan on block_lookup bl  (cost=0.00..4.00 rows=200 width=42) (actual time=0.656..3.060 rows=327 loops=1)
                                            Buffers: shared hit=1348
                          ->  Hash  (cost=38.79..38.79 rows=200 width=8) (actual time=1.773..1.774 rows=327 loops=1)
                                Buckets: 1024  Batches: 1  Memory Usage: 21kB
                                ->  HashAggregate  (cost=36.79..38.79 rows=200 width=8) (actual time=1.668..1.717 rows=327 loops=1)
                                      Group Key: block_bit_probes.cp_block_index
                                      Batches: 1  Memory Usage: 61kB
                                      ->  CTE Scan on block_bit_probes  (cost=0.00..32.70 rows=1635 width=8) (actual time=0.007..1.162 rows=1635 loops=1)
        ->  Index Scan using cp_blooms_pkey on cp_blooms cb  (cost=0.43..21883.74 rows=1 width=8) (actual time=0.040..0.408 rows=62 loops=1)
              Index Cond: ((cp_sequence_number >= ((block_bit_probes.cp_block_index * '1000'::bigint))) AND (cp_sequence_number <= ((((block_bit_probes.cp_block_index * '1000'::bigint) + '1000'::bigint) - 1))))
              Filter: (((get_byte(bloom_filter, (208 % length(bloom_filter))) & 8) = 8) AND ((get_byte(bloom_filter, (5988 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8084 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (15059 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8330 % length(bloom_filter))) & 64) = 64) AND ((get_byte(bloom_filter, (10313 % length(bloom_filter))) & 4) = 4))
              Rows Removed by Filter: 318
              Buffers: shared hit=311
Planning:
  Buffers: shared hit=78
Planning Time: 2.181 ms
Execution Time: 15.060 ms
```

## Stack:
#24788 
#24900 👈 
#25097
---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
@henryachen henryachen mentioned this pull request Mar 5, 2026
16 tasks
mystenmark pushed a commit that referenced this pull request Mar 5, 2026
## Description 

Adds a new transactionsScan query that scans checkpoints using bloom
filters to find transactions matching filter criteria.

  Changes

  GraphQL (sui-indexer-alt-graphql)
  - New transactionsScan query endpoint
  - maxScanLimit service config to limit checkpoints scanned per query
  - Transaction filter and scan logic in scan.rs
  
  Schema (sui-indexer-alt-schema)
- `bloom_contains` SQL function for checking probe membership in bloom
filters

  Reader (sui-indexer-alt-reader)
  - `cp_blooms` loader for batch-loading bloom filter data


## Test plan 

```
cargo nextest run -p sui-indexer-alt-graphql graphql_scan_limit_tests
cargo nextest run -p sui-indexer-alt-graphql
cargo nextest run -p sui-indexer-alt
cargo nextest run -p sui-indexer-alt-schema
```

Query Plan:
```
Limit  (cost=1911.69..1358704.33 rows=62 width=8) (actual time=14.454..14.836 rows=62 loops=1)
  Buffers: shared hit=7287
  CTE block_bit_probes
    ->  ProjectSet  (cost=0.00..8.20 rows=1635 width=18) (actual time=0.004..0.431 rows=1635 loops=1)
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
  ->  Nested Loop  (cost=1903.49..1358696.13 rows=62 width=8) (actual time=14.453..14.830 rows=62 loops=1)
        Buffers: shared hit=7287
        ->  Limit  (cost=1903.06..1903.22 rows=62 width=24) (actual time=14.411..14.414 rows=1 loops=1)
              Buffers: shared hit=6976
              CTE block_lookup
                ->  Nested Loop Left Join  (cost=41.30..1723.38 rows=200 width=131) (actual time=0.654..2.899 rows=327 loops=1)
                      Buffers: shared hit=1348
                      ->  HashAggregate  (cost=40.88..42.88 rows=200 width=10) (actual time=0.620..0.682 rows=327 loops=1)
                            Group Key: block_bit_probes_1.cp_block_index, block_bit_probes_1.bloom_idx
                            Batches: 1  Memory Usage: 61kB
                            ->  CTE Scan on block_bit_probes block_bit_probes_1  (cost=0.00..32.70 rows=1635 width=10) (actual time=0.000..0.159 rows=1635 loops=1)
                      ->  Index Scan using cp_bloom_blocks_pkey on cp_bloom_blocks bb  (cost=0.42..8.40 rows=1 width=131) (actual time=0.006..0.006 rows=1 loops=327)
                            Index Cond: ((cp_block_index = block_bit_probes_1.cp_block_index) AND (bloom_block_index = block_bit_probes_1.bloom_idx))
                            Buffers: shared hit=1343
              ->  Sort  (cost=179.69..180.17 rows=192 width=24) (actual time=14.410..14.412 rows=1 loops=1)
                    Sort Key: ((block_bit_probes.cp_block_index * '1000'::bigint))
                    Sort Method: quicksort  Memory: 25kB
                    Buffers: shared hit=6976
                    ->  Hash Right Anti Join  (cost=48.29..173.01 rows=192 width=24) (actual time=14.392..14.400 rows=5 loops=1)
                          Hash Cond: (p.cp_block_index = block_bit_probes.cp_block_index)
                          Buffers: shared hit=6976
                          ->  Hash Join  (cost=7.00..129.78 rows=8 width=8) (actual time=3.224..12.355 rows=1011 loops=1)
                                Hash Cond: ((p.cp_block_index = bl.cp_block_index) AND (p.bloom_idx = bl.bloom_idx))
                                Join Filter: ((bl.bloom_filter IS NULL) OR (p.bit_mask <> get_byte(bl.bloom_filter, (p.byte_pos % length(bl.bloom_filter)))))
                                Rows Removed by Join Filter: 624
                                Buffers: shared hit=6976
                                ->  CTE Scan on block_bit_probes p  (cost=0.00..32.70 rows=1635 width=18) (actual time=0.000..0.211 rows=1635 loops=1)
                                ->  Hash  (cost=4.00..4.00 rows=200 width=42) (actual time=3.142..3.142 rows=327 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 93kB
                                      Buffers: shared hit=1348
                                      ->  CTE Scan on block_lookup bl  (cost=0.00..4.00 rows=200 width=42) (actual time=0.656..3.060 rows=327 loops=1)
                                            Buffers: shared hit=1348
                          ->  Hash  (cost=38.79..38.79 rows=200 width=8) (actual time=1.773..1.774 rows=327 loops=1)
                                Buckets: 1024  Batches: 1  Memory Usage: 21kB
                                ->  HashAggregate  (cost=36.79..38.79 rows=200 width=8) (actual time=1.668..1.717 rows=327 loops=1)
                                      Group Key: block_bit_probes.cp_block_index
                                      Batches: 1  Memory Usage: 61kB
                                      ->  CTE Scan on block_bit_probes  (cost=0.00..32.70 rows=1635 width=8) (actual time=0.007..1.162 rows=1635 loops=1)
        ->  Index Scan using cp_blooms_pkey on cp_blooms cb  (cost=0.43..21883.74 rows=1 width=8) (actual time=0.040..0.408 rows=62 loops=1)
              Index Cond: ((cp_sequence_number >= ((block_bit_probes.cp_block_index * '1000'::bigint))) AND (cp_sequence_number <= ((((block_bit_probes.cp_block_index * '1000'::bigint) + '1000'::bigint) - 1))))
              Filter: (((get_byte(bloom_filter, (208 % length(bloom_filter))) & 8) = 8) AND ((get_byte(bloom_filter, (5988 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8084 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (15059 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8330 % length(bloom_filter))) & 64) = 64) AND ((get_byte(bloom_filter, (10313 % length(bloom_filter))) & 4) = 4))
              Rows Removed by Filter: 318
              Buffers: shared hit=311
Planning:
  Buffers: shared hit=78
Planning Time: 2.181 ms
Execution Time: 15.060 ms
```

## Stack:
#24788 
#24900 👈 
#25097
---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
jessiemongeon1 pushed a commit to jessiemongeon1/sui that referenced this pull request Mar 5, 2026
## Description 

Adds a new transactionsScan query that scans checkpoints using bloom
filters to find transactions matching filter criteria.

  Changes

  GraphQL (sui-indexer-alt-graphql)
  - New transactionsScan query endpoint
  - maxScanLimit service config to limit checkpoints scanned per query
  - Transaction filter and scan logic in scan.rs
  
  Schema (sui-indexer-alt-schema)
- `bloom_contains` SQL function for checking probe membership in bloom
filters

  Reader (sui-indexer-alt-reader)
  - `cp_blooms` loader for batch-loading bloom filter data


## Test plan 

```
cargo nextest run -p sui-indexer-alt-graphql graphql_scan_limit_tests
cargo nextest run -p sui-indexer-alt-graphql
cargo nextest run -p sui-indexer-alt
cargo nextest run -p sui-indexer-alt-schema
```

Query Plan:
```
Limit  (cost=1911.69..1358704.33 rows=62 width=8) (actual time=14.454..14.836 rows=62 loops=1)
  Buffers: shared hit=7287
  CTE block_bit_probes
    ->  ProjectSet  (cost=0.00..8.20 rows=1635 width=18) (actual time=0.004..0.431 rows=1635 loops=1)
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
  ->  Nested Loop  (cost=1903.49..1358696.13 rows=62 width=8) (actual time=14.453..14.830 rows=62 loops=1)
        Buffers: shared hit=7287
        ->  Limit  (cost=1903.06..1903.22 rows=62 width=24) (actual time=14.411..14.414 rows=1 loops=1)
              Buffers: shared hit=6976
              CTE block_lookup
                ->  Nested Loop Left Join  (cost=41.30..1723.38 rows=200 width=131) (actual time=0.654..2.899 rows=327 loops=1)
                      Buffers: shared hit=1348
                      ->  HashAggregate  (cost=40.88..42.88 rows=200 width=10) (actual time=0.620..0.682 rows=327 loops=1)
                            Group Key: block_bit_probes_1.cp_block_index, block_bit_probes_1.bloom_idx
                            Batches: 1  Memory Usage: 61kB
                            ->  CTE Scan on block_bit_probes block_bit_probes_1  (cost=0.00..32.70 rows=1635 width=10) (actual time=0.000..0.159 rows=1635 loops=1)
                      ->  Index Scan using cp_bloom_blocks_pkey on cp_bloom_blocks bb  (cost=0.42..8.40 rows=1 width=131) (actual time=0.006..0.006 rows=1 loops=327)
                            Index Cond: ((cp_block_index = block_bit_probes_1.cp_block_index) AND (bloom_block_index = block_bit_probes_1.bloom_idx))
                            Buffers: shared hit=1343
              ->  Sort  (cost=179.69..180.17 rows=192 width=24) (actual time=14.410..14.412 rows=1 loops=1)
                    Sort Key: ((block_bit_probes.cp_block_index * '1000'::bigint))
                    Sort Method: quicksort  Memory: 25kB
                    Buffers: shared hit=6976
                    ->  Hash Right Anti Join  (cost=48.29..173.01 rows=192 width=24) (actual time=14.392..14.400 rows=5 loops=1)
                          Hash Cond: (p.cp_block_index = block_bit_probes.cp_block_index)
                          Buffers: shared hit=6976
                          ->  Hash Join  (cost=7.00..129.78 rows=8 width=8) (actual time=3.224..12.355 rows=1011 loops=1)
                                Hash Cond: ((p.cp_block_index = bl.cp_block_index) AND (p.bloom_idx = bl.bloom_idx))
                                Join Filter: ((bl.bloom_filter IS NULL) OR (p.bit_mask <> get_byte(bl.bloom_filter, (p.byte_pos % length(bl.bloom_filter)))))
                                Rows Removed by Join Filter: 624
                                Buffers: shared hit=6976
                                ->  CTE Scan on block_bit_probes p  (cost=0.00..32.70 rows=1635 width=18) (actual time=0.000..0.211 rows=1635 loops=1)
                                ->  Hash  (cost=4.00..4.00 rows=200 width=42) (actual time=3.142..3.142 rows=327 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 93kB
                                      Buffers: shared hit=1348
                                      ->  CTE Scan on block_lookup bl  (cost=0.00..4.00 rows=200 width=42) (actual time=0.656..3.060 rows=327 loops=1)
                                            Buffers: shared hit=1348
                          ->  Hash  (cost=38.79..38.79 rows=200 width=8) (actual time=1.773..1.774 rows=327 loops=1)
                                Buckets: 1024  Batches: 1  Memory Usage: 21kB
                                ->  HashAggregate  (cost=36.79..38.79 rows=200 width=8) (actual time=1.668..1.717 rows=327 loops=1)
                                      Group Key: block_bit_probes.cp_block_index
                                      Batches: 1  Memory Usage: 61kB
                                      ->  CTE Scan on block_bit_probes  (cost=0.00..32.70 rows=1635 width=8) (actual time=0.007..1.162 rows=1635 loops=1)
        ->  Index Scan using cp_blooms_pkey on cp_blooms cb  (cost=0.43..21883.74 rows=1 width=8) (actual time=0.040..0.408 rows=62 loops=1)
              Index Cond: ((cp_sequence_number >= ((block_bit_probes.cp_block_index * '1000'::bigint))) AND (cp_sequence_number <= ((((block_bit_probes.cp_block_index * '1000'::bigint) + '1000'::bigint) - 1))))
              Filter: (((get_byte(bloom_filter, (208 % length(bloom_filter))) & 8) = 8) AND ((get_byte(bloom_filter, (5988 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8084 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (15059 % length(bloom_filter))) & 32) = 32) AND ((get_byte(bloom_filter, (8330 % length(bloom_filter))) & 64) = 64) AND ((get_byte(bloom_filter, (10313 % length(bloom_filter))) & 4) = 4))
              Rows Removed by Filter: 318
              Buffers: shared hit=311
Planning:
  Buffers: shared hit=78
Planning Time: 2.181 ms
Execution Time: 15.060 ms
```

## Stack:
MystenLabs#24788 
MystenLabs#24900 👈 
MystenLabs#25097
---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] Indexing Framework:
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.

4 participants