Skip to content

feat(solana-indexer): PR 3 — add traits module#4508

Open
tilacog wants to merge 6 commits into
solana-indexer/PR2-bootstrapfrom
solana-indexer/PR3-bootstrap
Open

feat(solana-indexer): PR 3 — add traits module#4508
tilacog wants to merge 6 commits into
solana-indexer/PR2-bootstrapfrom
solana-indexer/PR3-bootstrap

Conversation

@tilacog

@tilacog tilacog commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the traits/ module as laid out in the PR sequence plan, carrying over the two traits that sit on the indexer's external boundaries.

The trait surface stays small on purpose: each one only covers what the consumer components actually need, not the full API of the underlying library. That keeps the abstractions thin and tied to real call sites.

Changes

  • Added traits/store.rs:
    • Store trait, the PostgreSQL boundary.
  • Added traits/solana_client.rs:
    • SolanaClient trait, the Solana RPC boundary.
  • Skipped a trait for the Yellowstone gRPC system boundary. That one works better as a GrpcConnector trait bound on the Ingester struct (coming in the next PR).

How to test

  1. Build the crate: cargo check -p solana-indexer.
  2. Check that the new traits module is exported and both traits are reachable from the crate root.
  3. Stub implementations land in follow-up PRs, so there's no runtime behavior to verify here yet.

This is a follow up PR to #4506

Introduces the traits/ module per the PR-sequence plan. The trait surface is intentionally narrow:
it covers only what the consumer components need, not the full surface of the underlying library.

traits/store.rs:
- Store (PostgreSQL boundary)

traits/solana_client.rs:
- SolanaClient (RPC boundary)

We don't need a trait to represent the Yelllowstone gRPC system boundary because that can be
represented by a trait bound in the (upcoming) Ingester struct implementation.
@tilacog tilacog requested a review from a team as a code owner June 9, 2026 01:41

@gemini-code-assist gemini-code-assist 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

This pull request introduces the SolanaClient and Store traits to abstract external dependencies (Solana RPC and PostgreSQL persistence) for the solana-indexer crate, along with updating dependency configurations and refactoring recovery-flow types. Feedback on the Store trait suggests returning Result<(), StoreError> instead of () for write_dead_letter and record_lost_slot_range to ensure robust error handling for database operations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/solana-indexer/src/traits/store.rs Outdated
@socket-security

socket-security Bot commented Jun 9, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: cargo libc is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?cargo/axum@0.8.8cargo/moka@0.12.12cargo/reqwest@0.12.28cargo/tempfile@3.24.0cargo/ruint@1.17.2cargo/tokio@1.49.0cargo/time@0.3.47cargo/reqwest@0.13.2cargo/solana-sdk@4.0.1cargo/alloy-contract@1.7.3cargo/alloy-provider@1.7.3cargo/alloy-primitives@1.5.7cargo/alloy-sol-macro-expander@1.5.7cargo/alloy-sol-macro-input@1.5.7cargo/aws-config@1.8.15cargo/aws-sdk-s3@1.127.0cargo/alloy-transport-ws@1.8.3cargo/alloy-signer@1.8.3cargo/alloy-consensus@1.8.3cargo/alloy-rpc-client@1.8.3cargo/alloy-signer-local@1.8.3cargo/alloy-eips@1.8.3cargo/alloy-transport@1.8.3cargo/alloy-provider@1.8.3cargo/alloy@1.8.3cargo/rand@0.10.1cargo/rand@0.9.4cargo/rand@0.8.6cargo/async-nats@0.48.0cargo/yellowstone-grpc-proto@12.4.0cargo/solana-client@4.0.0cargo/sqlx@0.8.6cargo/sha2@0.10.9cargo/dashmap@5.5.3cargo/dashmap@6.1.0cargo/axum@0.6.20cargo/hmac@0.12.1cargo/prometheus@0.14.0cargo/mimalloc@0.1.48cargo/chrono@0.4.42cargo/console-subscriber@0.3.0cargo/jemalloc_pprof@0.8.1cargo/opentelemetry-otlp@0.31.0cargo/backtrace@0.3.76cargo/const-hex@1.17.0cargo/tikv-jemallocator@0.6.1cargo/libc@0.2.186

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/libc@0.2.186. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: cargo openssl is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?cargo/sqlx@0.8.6cargo/openssl@0.10.80

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/openssl@0.10.80. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@tilacog tilacog changed the title feat(solana-indexer): traits/ module feat(solana-indexer): add traits module Jun 9, 2026
@tilacog tilacog marked this pull request as draft June 9, 2026 11:00
@tilacog tilacog marked this pull request as ready for review June 9, 2026 11:35

@gemini-code-assist gemini-code-assist 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

This pull request introduces the SolanaClient and Store traits to abstract external Solana RPC calls and PostgreSQL persistence for the indexer. It also refactors related types and dependencies. The feedback suggests using std::ops::RangeInclusive<u64> instead of std::ops::Range<u64> in the Store trait to avoid potential off-by-one errors when recording lost slot ranges.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/solana-indexer/src/traits/store.rs Outdated
@socket-security

socket-security Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedsolana-client@​4.0.010010093100100
Addedbase64@​0.13.110010093100100
Addedrand@​0.8.610010093100100
Addeddashmap@​5.5.310010093100100
Addednum@​0.2.110010093100100
Updatedarc-swap@​1.8.0 ⏵ 1.9.110010093100100

View full report

@github-actions

Copy link
Copy Markdown

This pull request has been marked as stale because it has been inactive a while. Please update this pull request or it will be automatically closed.

@github-actions github-actions Bot added the stale label Jun 17, 2026
Comment on lines +23 to +24
/// Record a slot checkpoint. Rejects downward writes.
async fn write_watermark(&self, slot: u64) -> Result<(), StoreError>;

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.

What does "reject downward writes" mean?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It means it should reject slot values smaller than what's already stored, so we never rewind the watermark by accident.

This is also something we're planning to enforce as database constraints.

};

/// PostgreSQL persistence. Used by Decoder, Watchdog, and FinalizationWorker.
pub trait Store {

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.

Can you elaborate a bit on why making this a trait is good?
Is the intention to implement a custom persistence layer for testing purposes or something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I mostly wanted to follow dependency inversion so the pieces stay decoupled.
It also makes it easier to use mocks in our tests, which is a nice plus.

I am only planning on using Postgres for now, so it is really just about keeping the logic separate from the storage layer. (This applies to all the traits here, btw)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

And, to be fair, I noticed this specific trait is getting too crowded w/ methods.

I thought about splitting it into smaller sub-traits, culminating into a Store: A + B + C super- trait, but I was worried about making it more complicated than it needs to be.

I think we should refine that idea before merging this

@tilacog tilacog changed the title feat(solana-indexer): add traits module feat(solana-indexer): PR 3 — add traits module Jun 23, 2026
Comment on lines +35 to +44
/// Primary promotion pass: fetch `confirmed` rows whose `slot` is at or
/// above the finalization-window threshold (`slot >= newer_than_slot`).
/// `limit` caps the batch at 256 (RPC batch size). Returns `Err` on
/// backend failure so the caller can back off rather than
/// silently stall on a dead store.
async fn get_confirmed_rows(
&self,
newer_than_slot: u64,
limit: usize,
) -> Result<Vec<UnfinalizedRow>, StoreError>;

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.

This returns the newest rows (slot >= newer_than_slot), but do we want the oldest here? A transaction takes ~32 slots to finalize, so polling anything newer just wastes RPC calls. Should the bound be slot <= threshold?

@tilacog tilacog Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're correct.
We expect the finalization pass to manage the newest rows, so that method should only care for the slots that weren't handled by the finalization pass.

I think it this would be better off like this:

/// Primary promotion pass: fetch `confirmed` rows whose `slot` is old enough
/// to be finalized (typically `slot <= tip - 32`) but new enough to still
/// be tracked by the RPC retention horizon.
///
/// `limit` caps the batch at 256 (RPC batch size). Returns `Err` on
/// backend failure so the caller can back off rather than
/// silently stall on a dead store.
async fn get_confirmed_rows(
    &self,
    max_slot: u64,  // <-- renamed after `newer_than_slot`
    limit: usize,
) -> Result<Vec<UnfinalizedRow>, StoreError>;

While we're on this, I believe this should now use the Slot type introduced in the previous PR

…tstrap

Resolve conflicts from the updated PR2 base:
- adopt Slot/OrderUid newtypes in channel.rs and recovery.rs
- drop observe/prometheus deps (metrics.rs removed on PR2), keep solana-client
- make Store/SolanaClient pub(crate) to match PR2's type visibility (a pub trait over pub(crate) types is E0446)
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.

3 participants