feat: discovery-grade events for delegating validators - #7
Conversation
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.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
There was a problem hiding this comment.
💡 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".
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.
There was a problem hiding this comment.
💡 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".
| for ix in &message.instructions { | ||
| observed.extend(parse(ix.program_id_index as usize, &ix.accounts, &ix.data)); | ||
| } | ||
| for inner in &meta.inner_instructions { |
There was a problem hiding this comment.
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 👍 / 👎.
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) andDelegateWithAnyValidator(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
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.