Skip to content

feat: discovery-grade events for delegating validators - #7

Open
GabrielePicco wants to merge 3 commits into
feat/firehose-modefrom
feat/discovery-events
Open

feat: discovery-grade events for delegating validators#7
GabrielePicco wants to merge 3 commits into
feat/firehose-modefrom
feat/discovery-events

Conversation

@GabrielePicco

Copy link
Copy Markdown
Contributor

Summary

Stacked on #6 (base = feat/firehose-mode). Not intended for immediate merge — the validator pins this branch rev during development of its record-stream-driven discovery.

The record PDA is derived one-way from the delegated account, so record updates alone cannot tell a consumer what to fetch when a new delegation appears. This PR adds the two event classes a delegating validator needs to drop its DLP account-firehose subscription entirely:

DelegationObserved { delegated_account, record, slot }

Parsed from Delegate (0) and DelegateWithAnyValidator (19) instructions in the transaction stream, walking inner instructions — delegation is CPI-invoked from the owner program, so top-level-only parsing would miss essentially all of them. Instruction discriminators are compared as full little-endian u64s (the previous undelegate check compared only byte 0; strengthened here). Account indexes per the dlp-api instruction builder: delegated account at 1, record at 4, shared by both discriminators.

UndelegationRequested { delegated_account, expires_at_slot, slot }

A second discriminator-filtered account filter (AccountDiscriminator::UndelegationRequest = 104) is added to firehose subscriptions only; payloads on the shared stream are told apart by their account discriminator. This preserves real-time undelegation-request detection for validators that stop subscribing to the DLP program (today that detection rides the program sub; without it, requests degrade to a 300-second poll).

Also

  • Undelegate detection walks inner instructions too (same CPI reasoning).
  • Subscribed mode is unchanged: no extra filter, no new events (router unaffected beyond the enum additions).
  • Version 0.3.0 — source-breaking enum variants.

15 unit tests + doc test green (top-level/inner/any-validator delegate observation, unknown-discriminator rejection, undelegation-request decoding, subscribed-mode silence, filter shape); clippy/fmt clean.

The record PDA is derived one-way from the delegated account, so record
updates alone cannot drive delegation discovery. Firehose consumers now
additionally receive:

- DelegationObserved { delegated_account, record, slot }: parsed from
  Delegate / DelegateWithAnyValidator instructions in the transaction
  stream, walking inner instructions since delegation is CPI-invoked from
  the owner program. Instruction discriminators are compared as full
  little-endian u64s.
- UndelegationRequested { delegated_account, expires_at_slot, slot }:
  streamed from UndelegationRequest account writes via a second
  discriminator-filtered account filter (firehose subscriptions only),
  so a delegating validator can honor undelegation requests in real time
  without the DLP account firehose.

Undelegate detection also walks inner instructions now. Subscribed-mode
behavior is unchanged: no extra filter, no new events. Version 0.3.0 for
the source-breaking enum additions.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3f639fcb-a0ab-4210-b756-2be4d52af070

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a8235294e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/syncer.rs
Walking inner instructions made the transaction stream observe virtually
every undelegation on chain, so the previously dormant unconditional send
turned into a global feed for subscribed-mode consumers. Match the
account-side contract: buffer every undelegation for replay-on-subscribe,
deliver live only for records with an active subscription.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24814f6a38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/syncer.rs
Comment on lines +608 to +611
for ix in &message.instructions {
observed.extend(parse(ix.program_id_index as usize, &ix.accounts, &ix.data));
}
for inner in &meta.inner_instructions {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve execution order when merging inner instructions

When a transaction mixes top-level DLP calls with CPI-invoked DLP calls, these two passes emit every top-level operation before every inner operation, regardless of their actual execution order. For example, an undelegation invoked by instruction 0 followed by a top-level delegation in instruction 1 is reported as delegation then undelegation, so a firehose consumer applying events sequentially can finish with the opposite record state. Interleave each InnerInstructions group using its index immediately after the corresponding top-level instruction.

Useful? React with 👍 / 👎.

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.

1 participant