feat(solana-indexer): PR 3 — add traits module#4508
Conversation
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.
There was a problem hiding this comment.
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.
|
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.
|
traits module
There was a problem hiding this comment.
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.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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. |
| /// Record a slot checkpoint. Rejects downward writes. | ||
| async fn write_watermark(&self, slot: u64) -> Result<(), StoreError>; |
There was a problem hiding this comment.
What does "reject downward writes" mean?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
traits moduletraits module
- openssl 0.10.75 → 0.10.80 (fixes CVE-2026-41676/78/81/98, CVE-2026-42327, CVE-2026-44662, CVE-2026-45784, CVE-2026-41677) - rand 0.8.5 → 0.8.6 (fixes GHSA-cq8v-f236-94qc)
| /// 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>; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
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
traits/store.rs:Storetrait, the PostgreSQL boundary.traits/solana_client.rs:SolanaClienttrait, the Solana RPC boundary.GrpcConnectortrait bound on theIngesterstruct (coming in the next PR).How to test
cargo check -p solana-indexer.traitsmodule is exported and both traits are reachable from the crate root.This is a follow up PR to #4506