Skip to content

feat(solana-indexer): PR 2 — add types module scaffolding #4506

Open
tilacog wants to merge 23 commits into
mainfrom
solana-indexer/PR2-bootstrap
Open

feat(solana-indexer): PR 2 — add types module scaffolding #4506
tilacog wants to merge 23 commits into
mainfrom
solana-indexer/PR2-bootstrap

Conversation

@tilacog

@tilacog tilacog commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the type definitions for the types/ module listed in the crate-layout spec.

This is PR 2 in the sequence; later PRs (traits (PR 3), then component structs (PR 4)) consume these types.

Changes

  • Added crates/solana-indexer/src/types/ with one file per module from the spec:

    • wire.rs: wire-format types, for the Yellowstone gRPC protobuf types
    • channel.rs: payload types for messages exchanged between the Ingester and Decoder component
    • events.rs: domain event taxonomy
    • commitment.rs: types for tracking Solana commitment levels
    • dead_letter.rs: dead-letter primitives
    • recovery.rs: recovery state
    • errors.rs: error types for this crate
    • tx.rs: transaction context
  • Declared pub mod types; in lib.rs so the module compiles.

How to test

  1. cargo check -p solana-indexer
  2. cargo +nightly fmt --all -- --check
  3. cargo clippy --locked --workspace --all-features --all-targets -- -D warnings

No unit tests are included: the modules contain only type definitions, with no logic to exercise until the traits in PR 3 are introduced.

@tilacog tilacog requested a review from a team as a code owner June 8, 2026 20:47

@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 solana-indexer crate, which defines domain types for a Solana settlement indexer, including channel messages, commitment tracking, dead-letter handling, errors, events, metrics, recovery flows, and transaction helpers. A high-severity issue was identified in channel.rs where the PartialEvent struct is missing the half: PartialHalf field described in its documentation, which is required for the watchdog to reconstruct events. Adding this field also means PartialEvent must derive Clone instead of Copy due to heap-allocated types in PartialHalf.

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 on lines +44 to +50
#[derive(Debug, Clone, Copy)]
pub struct PartialEvent {
/// Slot the partial was observed at.
pub slot: u64,
/// Transaction signature the partial corresponds to.
pub signature: Signature,
}

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.

high

The doc comment for PartialEvent states that "each delivery carries the half that just landed," but the struct definition is missing the half: PartialHalf field. Without this field, the watchdog cannot receive or hold the actual data halves to match and reconstruct the full event. Additionally, since PartialHalf contains heap-allocated Box types, PartialEvent cannot derive Copy and should only derive Debug, Clone.

Suggested change
#[derive(Debug, Clone, Copy)]
pub struct PartialEvent {
/// Slot the partial was observed at.
pub slot: u64,
/// Transaction signature the partial corresponds to.
pub signature: Signature,
}
#[derive(Debug, Clone)]
pub struct PartialEvent {
/// Slot the partial was observed at.
pub slot: u64,
/// Transaction signature the partial corresponds to.
pub signature: Signature,
/// The half that just landed.
pub half: PartialHalf,
}

@tilacog tilacog changed the title feat(solana-indexer): add types module scaffolding feat(solana-indexer): add types module scaffolding Jun 8, 2026
tilacog added 2 commits June 8, 2026 18:06
Local tombi was 0.9.0; CI uses 1.1.0 which produces different output.
@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 16, 2026
Comment on lines +47 to +53
#[error("replay window exceeded")]
ReplayWindowExceeded {
/// The slot the subscriber attempted to resume from.
attempted_slot: u64,
/// The earliest slot the provider can still serve.
earliest_replayable_slot: u64,
},

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.

aren't you losing the slot u64's here?

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.

Thanks, I was so focused on the types that I completely forgot to include the fields in the error message. Fixed in bfb4dcf.

Comment on lines +34 to +35
/// String label used in `solana.dead_letter.reason`.
pub fn as_str(self) -> &'static str {

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 would be more accurate, i see "as_str" like a "const to_string" and I'd expect it to return the same as the label itself e.g. DecoderError -> "DecoderError"

Suggested change
/// String label used in `solana.dead_letter.reason`.
pub fn as_str(self) -> &'static str {
/// String label used in `solana.dead_letter.reason`.
pub fn as_label(self) -> &'static str {

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.

Renamed in 6e1f41e

Comment on lines +19 to +26
/// String label used in `solana.*` `commitment` columns.
pub fn as_str(self) -> &'static str {
match self {
Self::Confirmed => "confirmed",
Self::Finalized => "finalized",
Self::RolledBack => "rolled_back",
}
}

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.

Suggested change
/// String label used in `solana.*` `commitment` columns.
pub fn as_str(self) -> &'static str {
match self {
Self::Confirmed => "confirmed",
Self::Finalized => "finalized",
Self::RolledBack => "rolled_back",
}
}
/// String label used in `solana.*` `commitment` columns.
pub fn as_label(self) -> &'static str {
match self {
Self::Confirmed => "confirmed",
Self::Finalized => "finalized",
Self::RolledBack => "rolled_back",
}
}

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.

Renamed in 6e1f41e

use {crate::types::Signature, solana_sdk::pubkey::Pubkey};

/// On-chain commitment of a transaction or row.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

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.

Do we actually need all these now? IMO we should just start with debug + clone (and maybe copy i guess) but the eq's will make more sense when actually used

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 right, they were added "just in case," but future PRs only use this type in match expressions, so the derives aren't needed.

Removed in 0fef0ed

Comment on lines +57 to +62
pub enum PartialHalf {
/// Transaction-update half.
Tx(Box<SubscribeUpdateTransactionInfo>),
/// Account-update half.
Account(Box<SubscribeUpdateAccountInfo>),
}

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.

why do these need to be boxed?

@tilacog tilacog Jun 23, 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.

They are big types, and clippy caught that in its large_enum_variant lint.

  • SubscribeUpdateAccountInfo: 128 bytes
  • SubscribeUpdateTransactionInfo: 520 bytes (determines the enum size)

Mind that those values are in the hot path, so we expect them to be moved/copied a lot.

Comment on lines +15 to +16
/// at. The org file names the channel payload "Event"; the spec defines that
/// type as `StreamUpdate`, and that is what this crate uses.

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.

Please update those comments. AI always writes doc comments which reference context that is not present in the PR or the final code base.

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.

Thanks for catching that, it shouldn't have been in there.
Removed in f57158f.

I do want to move the spec into the repo eventually, but I haven't landed on the right format yet.

/// An account-update slot message.
Account {
/// Slot the message was observed at.
slot: u64,

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.

Is slots solana lingo for blocks?

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.

Precisely.
A slot can be seen as a spot reserved for a block, for which the validator can include a block, or not.

More details here.

Comment on lines +1 to +2
//! Commitment-tracking types: confirmation state, signature status, and the row
//! shapes consumed by the finalization worker.

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.

Maybe an explainer on what a commitment is in this context is more helpful.

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've expanded the module docs in 399c2ec to give more context.


use {crate::types::Signature, solana_sdk::pubkey::Pubkey};

/// On-chain commitment of a transaction or row.

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.

Does "row" mean a DB row here? In that case it seems like this should maybe not live in the fundamental "types" directory.

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.

Yeah, it's a DB row.

I figured it was fine to keep this type here since it's only meant for the finalization worker component.

It will be used only to bump the commitment level on most rows in the database (every solana.* row comes from a transaction).


/// On-chain commitment of a transaction or row.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Commitment {

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.

commitment needs a better explanation here. It sounds like this is just the outcome of a tx but it's impossible to tell based on the existing information.

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've expanded the comments in 72146c4 to explain a bit about each commitment level

/// Transaction signature.
tx_signature: Signature,
/// Slot the settlement was observed at.
slot: u64,

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 code could be more self explanatory with Slot(u64) and observed_at: Slot.

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.

Agreed.
Introduced the Slot type in 57b5c76

/// Transaction signature.
tx_signature: Signature,
/// Index of the instruction within the transaction.
ix_index: u8,

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.

Is the tx index used often enough that a new type would make sense for this?

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 don't think so.
That info is mostly useful for auditing.

The SolverInteraction variant is still something we want to keep track of and index, but we don't really have a way to interpret it since it's basically an arbitrary instruction.

The upside is that, if we ever decide to try to retroactively decode a subset of those, we can refer to this info.

/// Real owner of the order.
real_owner: Pubkey,
/// Order UID.
order_uid: [u8; 32],

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.

GIven that intent hash and order uid have the same data "shape" they should definitely get their own separate types.

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've introduced a new OrderUid type in c7936f4 to represent both intent_hash and order_uid fields, since they represent the same concept.

OrderEnabled {
/// Custodial PDA.
custodial_pda: Pubkey,
/// Address that enabled the order.

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 "enabled" here mean? Is this a contract that calls a function to activate the order or perhaps an account that pays for the gas or something else entirely?

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've expanded the doc-comments in 271539d to clarify those roles.

You intuition is correct, the enabler is an unprivileged actor that subsidizes the the order creation.

#[metric(subsystem = "solana_indexer")]
pub struct Metrics {
/// Slots between the chain tip and the indexed watermark.
pub indexer_lag_slots: GenericGauge<AtomicI64>,

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.

Does it make sense that a lag gets a signed integer gauge?
Same applies to other metrics which all seem to work just fine with unsigned ints.

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.

That's true, it was an oversight from my part.

After considering this other review comment, I decided to not include the metrics in this PR, but I'll keep the signing in mind when reintroducing them.

@tilacog tilacog changed the title feat(solana-indexer): add types module scaffolding feat(solana-indexer) PR 2: add types module scaffolding Jun 23, 2026
@tilacog tilacog changed the title feat(solana-indexer) PR 2: add types module scaffolding feat(solana-indexer): PR 2 — add types module scaffolding Jun 23, 2026
@socket-security

socket-security Bot commented Jun 23, 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
Addedyellowstone-grpc-proto@​12.4.08110093100100
Addedsolana-sdk@​4.0.110010093100100

View full report

@github-actions github-actions Bot removed the stale label Jun 24, 2026

@squadgazzz squadgazzz 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.

LGTM in overall. Any discrepancies can be adjusted in further PRs.

/// Prometheus metrics for the solana-indexer.
#[derive(prometheus_metric_storage::MetricStorage)]
#[metric(subsystem = "solana_indexer")]
pub struct Metrics {

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.

Not sure if metrics need to be a part of this PR. Maybe it makes sense to introduce them only when actually used? Right now, it is hard to judge whether the types and labels are correctly chosen.

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.

That's a fair point. I've removed them in 6b2e8a1.
We'll add them in the follow up PRs, as needed.

/// Transaction signature.
tx_signature: Signature,
/// Index of the instruction within the transaction.
ix_index: u8,

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.

According to the docs, it should be u16. Which one should be used?

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.

Thanks, let's go w/ u16.
Fixed in 47f8edf
I don't think we'll ever go beyond 256 instructions, but I don't want to be caught by surprise here.

#[derive(Debug, Clone)]
pub struct PdaSnapshot {
/// Order UID.
pub order_uid: [u8; 32],

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.

As @MartinquaXD already suggested for other types, maybe we need an OrderUid type here as well.

@tilacog tilacog Jun 24, 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.

I've introduced a new OrderUid type in c7936f4 to represent both intent_hash and order_uid fields, since they represent the same concept.

tilacog added 8 commits June 24, 2026 11:41
Switches raw/serialized byte fields from Vec<u8> to bytes::Bytes so cheap clones are available from the start.

Fields changed: AccountInfo.data, DeadLetterEntry.raw_bytes, ResolvedInstruction.data. ResolvedInstruction.accounts stays Vec<u8> because it stores account indices, not opaque bytes.
Introduces `types::Slot`, a thin newtype wrapper around `u64`, and
replaces the bare `u64` slot fields in the event, channel, transaction,
commitment, and dead-letter types with it.
…fiers

Add a single `OrderUid(pub [u8; 32])` newtype in `types/mod.rs` and use
it in place of raw `[u8; 32]` for every order-identifier field across
`TradeDelta`, the settlement-program lifecycle events, the SolFlow
custody events, and `PdaSnapshot`.
tilacog added 3 commits June 24, 2026 14:28
- Change all domain types from pub to pub(crate) — these are internal
  implementation details that shouldn't leak into the public API
- Add #![expect(dead_code)] to each types module file to suppress
  warnings during bootstrap phase when not all types are wired up yet
- Add #[allow(clippy::enum_variant_names)] on SolFlowEvent to suppress
  false positive from semantically correct variant names
…dules

Move Slot to types/slot.rs and OrderUid to types/order.rs, leaving
types/mod.rs as pure module declarations + the Signature re-export.
Consistent with the crate's one-concept-per-file pattern.

@squadgazzz squadgazzz 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.

LGTM

tilacog and others added 3 commits June 25, 2026 09:43
Spec §9 Domain event taxonomy types SolverInteraction.ix_index as u16.
Bump both the event field (events.rs) and the upstream decoder view
(ResolvedInstruction.ix_index) to u16 for consistent typing through the
pipeline; the single downcast from the grpc u32 lands at the future
construction site.
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