Skip to content

feat: ephemeral cranks#9

Open
Dodecahedr0x wants to merge 35 commits into
mainfrom
dode/ephemeral-cranks
Open

feat: ephemeral cranks#9
Dodecahedr0x wants to merge 35 commits into
mainfrom
dode/ephemeral-cranks

Conversation

@Dodecahedr0x

@Dodecahedr0x Dodecahedr0x commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Closes #8

Summary by CodeRabbit

  • New Features

    • Added support for two Hydra modes: base and ephemeral.
    • Introduced a unified build/test workflow with simple make commands and a new CI pipeline.
    • Added end-to-end testing for live ephemeral crank scenarios.
  • Bug Fixes

    • Improved crank scheduling and trigger handling to keep local state in sync after firing.
    • Updated reward, timeout, and close behavior to match the selected mode.
    • Refined example and test setups to use the correct program IDs and account formats.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds a second on-chain program, hydra-ephemeral, targeting MagicBlock ephemeral rollups, sharing scheduling/serialization/verification logic with the base hydra program via a new shared hydra_api::program::processor module. Constants, PDAs, and CPI helpers are split into base/ephemeral namespaces; the cranker gains an --ephemeral mode; examples, tests, an e2e suite, CI workflow, and Makefile are added/updated accordingly.

Changes

Ephemeral Rollup Crank Feature

Layer / File(s) Summary
Shared constants, PDA derivation, and instruction wire format
crates/hydra-api/src/consts.rs, crates/hydra-api/src/lib.rs, crates/hydra-api/src/state.rs, crates/hydra-api/src/instruction.rs, crates/hydra-api/Cargo.toml
Splits fee/reward/staleness constants and program IDs into base/ephemeral modules, adds find_base_crank_pda/find_ephemeral_crank_pda, and reworks SchedMeta/ScheduledIx/CreateArgs to use byte-array program IDs with new serialization helpers and client::base/client::ephemeral builders.
CPI wrapper helpers for base and ephemeral
crates/hydra-api/src/cpi.rs
Introduces base::native, base::pinocchio, ephemeral::native, ephemeral::pinocchio CPI wrappers for create/cancel/close with signer-seed support.
Shared on-chain processor helpers
crates/hydra-api/src/program/*
Adds shared authorization, header parsing, region measurement, PDA derivation, tail serialization, follow-up verification, and process_cancel/process_close functions reused by both programs.
New hydra-ephemeral on-chain program
programs/hydra-ephemeral/*
Adds the crate with entrypoint dispatch and create/trigger/cancel/close processors integrating Magic-program CPI for ephemeral account lifecycle.
hydra base program delegates to shared processor
programs/hydra/src/*
Refactors cancel/close/create/trigger to call shared processor functions, switches account slices to immutable, and updates crate re-exports.
Cranker ephemeral mode support
crates/hydra-cranker/src/*
Adds mode.rs, --ephemeral CLI flag, mode-aware instruction/program-id selection, interval_slots decoding, and advance_after_trigger for local schedule replay.
Example programs updated for base/ephemeral CPI paths
examples/anchor/*, examples/native/*, examples/pinocchio/*
Updates CPI import paths, program IDs, byte-array ScheduledIx encoding, and PDA derivation to base-specific helpers.
Mollusk unit tests for base/ephemeral behavior
tests/lib.rs, tests/programs/noop/*
Adds ephemeral test helpers, updates scheduled-ix byte encoding across tests, adds an ephemeral trigger test, and adds crank-id logging to the noop program.
Live end-to-end ephemeral crank test suite
tests/e2e/*
Adds a new crate that boots real validator/rollup/cranker processes and verifies scheduled ephemeral cranks fire as expected.
CI workflow, Makefile, workspace config, and README updates
.github/workflows/ci.yml, Makefile, Cargo.toml, .gitignore, README.md
Adds CI jobs, build/test/lint make targets, workspace member/dependency updates, and documentation for the new ephemeral program and cranker usage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: GabrielePicco

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and directly reflects the main change: adding support for ephemeral cranks.
Linked Issues check ✅ Passed The PR implements ephemeral crank support and the related API, program, and test changes requested by issue #8.
Out of Scope Changes check ✅ Passed The additional CI, README, Makefile, and example updates are supporting changes for the ephemeral crank feature rather than unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dode/ephemeral-cranks

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.

@Dodecahedr0x Dodecahedr0x changed the base branch from main to dode/multiple-instructions July 1, 2026 14:01
@Dodecahedr0x Dodecahedr0x changed the base branch from dode/multiple-instructions to main July 6, 2026 14:10
@Dodecahedr0x Dodecahedr0x force-pushed the dode/ephemeral-cranks branch from 7b9b392 to 7e38e4a Compare July 7, 2026 08:55
@Dodecahedr0x Dodecahedr0x marked this pull request as ready for review July 7, 2026 09:36

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/ci.yml (1)

1-119: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add least-privilege permissions: and disable credential persistence.

Static analysis flags the default GITHUB_TOKEN permissions (unset → broad scope) and actions/checkout persisting credentials on the runner across all three jobs. Since none of these jobs need to write to the repo, tighten both.

🔒 Proposed hardening
 on:
   push:
     branches: [main]
   pull_request:
 
+permissions:
+  contents: read
+
 # Cancel superseded runs on the same ref to save runner minutes.
 concurrency:

And for each actions/checkout@v4 step:

       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 1 - 119, The CI workflow currently
relies on the default broad GITHUB_TOKEN scope and persists checkout credentials
in all jobs; tighten this by adding an explicit least-privilege permissions
block for the workflow/jobs and disabling credential persistence on each
actions/checkout@v4 step. Update the workflow definitions in the CI jobs (fmt,
default, e2e) and the checkout configuration so they do not retain repository
credentials after cloning.

Source: Linters/SAST tools

programs/hydra/src/processor/trigger.rs (1)

112-119: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use data_mut_ptr() for the write path
AccountView::data_ptr() is *const u8, but write_u64 takes *mut u8. Switch this back to data_mut_ptr() or add an explicit cast if the write is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@programs/hydra/src/processor/trigger.rs` around lines 112 - 119, The write
path in the trigger update block is using `AccountView::data_ptr()` even though
`write_u64` requires a mutable pointer. Update the `crank_ai` access in the
`unsafe` section to use `data_mut_ptr()` instead, or make the mutability
explicit if that is the intended write path, so the `write_u64` calls for
`OFF_NEXT_EXEC_SLOT`, `OFF_EXECUTED`, and `OFF_REMAINING` operate on a `*mut
u8`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 27-104: The CI workflow still uses mutable action refs, so update
the workflows in ci.yml to pin each third-party action to an immutable commit
SHA instead of tags or branches. Replace the uses entries for actions/checkout,
dtolnay/rust-toolchain, taiki-e/install-action, Swatinem/rust-cache, and
actions/setup-node with their corresponding commit-pinned versions, and keep the
existing step structure and names unchanged.

In `@crates/hydra-api/Cargo.toml`:
- Around line 33-37: `ephemeral-rollups-pinocchio` is currently always enabled
in `hydra-api` instead of being gated like the other optional dependencies.
Update `Cargo.toml` so this crate is declared optional and only activated
through the appropriate feature in the same way as `solana-define-syscall` and
`solana-program-error`, then wire it into the relevant feature set in
`hydra-api` (for example the `cpi-pinocchio` path or a dedicated `ephemeral`
feature) to match the intent described by `program/mod.rs`.

In `@crates/hydra-api/src/cpi.rs`:
- Around line 16-25: Update the doc example under hydra_api::cpi::native so it
matches the current CPI API: change the import path to
hydra_api::cpi::base::native and adjust the hydra_cpi::create example to include
the required signer_seeds argument. Keep the example aligned with the current
create signature in cpi.rs so the snippet copy-pastes and compiles.

---

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 1-119: The CI workflow currently relies on the default broad
GITHUB_TOKEN scope and persists checkout credentials in all jobs; tighten this
by adding an explicit least-privilege permissions block for the workflow/jobs
and disabling credential persistence on each actions/checkout@v4 step. Update
the workflow definitions in the CI jobs (fmt, default, e2e) and the checkout
configuration so they do not retain repository credentials after cloning.

In `@programs/hydra/src/processor/trigger.rs`:
- Around line 112-119: The write path in the trigger update block is using
`AccountView::data_ptr()` even though `write_u64` requires a mutable pointer.
Update the `crank_ai` access in the `unsafe` section to use `data_mut_ptr()`
instead, or make the mutability explicit if that is the intended write path, so
the `write_u64` calls for `OFF_NEXT_EXEC_SLOT`, `OFF_EXECUTED`, and
`OFF_REMAINING` operate on a `*mut u8`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e7404ae9-aebb-49ea-847c-4f3bb5e5d028

📥 Commits

Reviewing files that changed from the base of the PR and between 2718469 and 07d584a.

📒 Files selected for processing (49)
  • .github/workflows/ci.yml
  • .gitignore
  • Cargo.toml
  • Makefile
  • README.md
  • crates/hydra-api/Cargo.toml
  • crates/hydra-api/src/consts.rs
  • crates/hydra-api/src/cpi.rs
  • crates/hydra-api/src/instruction.rs
  • crates/hydra-api/src/lib.rs
  • crates/hydra-api/src/program/helpers.rs
  • crates/hydra-api/src/program/mod.rs
  • crates/hydra-api/src/program/processor.rs
  • crates/hydra-api/src/state.rs
  • crates/hydra-cranker/src/cache.rs
  • crates/hydra-cranker/src/fire.rs
  • crates/hydra-cranker/src/main.rs
  • crates/hydra-cranker/src/mode.rs
  • examples/anchor/Anchor.toml
  • examples/anchor/programs/hydra-example-anchor/Cargo.toml
  • examples/anchor/programs/hydra-example-anchor/src/lib.rs
  • examples/anchor/programs/hydra-example-anchor/tests/mollusk.rs
  • examples/native/src/lib.rs
  • examples/native/tests/mollusk.rs
  • examples/pinocchio/src/lib.rs
  • examples/pinocchio/tests/mollusk.rs
  • programs/hydra-ephemeral/Cargo.toml
  • programs/hydra-ephemeral/src/entrypoint.rs
  • programs/hydra-ephemeral/src/lib.rs
  • programs/hydra-ephemeral/src/processor/cancel.rs
  • programs/hydra-ephemeral/src/processor/close.rs
  • programs/hydra-ephemeral/src/processor/common.rs
  • programs/hydra-ephemeral/src/processor/create.rs
  • programs/hydra-ephemeral/src/processor/mod.rs
  • programs/hydra-ephemeral/src/processor/trigger.rs
  • programs/hydra/Cargo.toml
  • programs/hydra/src/entrypoint.rs
  • programs/hydra/src/lib.rs
  • programs/hydra/src/processor/cancel.rs
  • programs/hydra/src/processor/close.rs
  • programs/hydra/src/processor/common.rs
  • programs/hydra/src/processor/create.rs
  • programs/hydra/src/processor/mod.rs
  • programs/hydra/src/processor/trigger.rs
  • tests/e2e/Cargo.toml
  • tests/e2e/tests/ephemeral_cranks.rs
  • tests/lib.rs
  • tests/programs/noop/Cargo.toml
  • tests/programs/noop/src/lib.rs

Comment thread .github/workflows/ci.yml
Comment on lines +27 to +104
- uses: actions/checkout@v4

- name: Install Rust (rustfmt)
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.91.1
components: rustfmt

- name: Check formatting
run: make fmt-check

# ---------------------------------------------------------------------------
# Default feature set: lint + tests with the `ephemeral` feature OFF.
# ---------------------------------------------------------------------------
default:
name: lint + test (default)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust (clippy)
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.91.1
components: clippy,rustfmt

- name: Install Solana toolchain (build-sbf)
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/${SOLANA_VERSION}/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"

- name: Install cargo-nextest
uses: taiki-e/install-action@nextest

- uses: Swatinem/rust-cache@v2

- name: Default CI checks
run: make ci

# ---------------------------------------------------------------------------
# Live end-to-end: boots the real three-process stack (mb-test-validator +
# ephemeral-validator + hydra-cranker) and asserts ephemeral cranks fire on
# schedule. The validators ship as the `@magicblock-labs/ephemeral-validator`
# npm package (mb-test-validator wraps `solana-test-validator`, which the Anza
# toolchain provides). The `tests/e2e` crate is its own `[workspace]`.
# ---------------------------------------------------------------------------
e2e:
name: e2e (ephemeral cranks, live validators)
runs-on: ubuntu-latest
env:
# Pin to the version the test was developed against.
EPHEMERAL_VALIDATOR_VERSION: 0.13.3
steps:
- uses: actions/checkout@v4

- name: Install Rust (clippy)
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.91.1
components: clippy

- name: Install Solana toolchain (build-sbf + solana-test-validator)
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/${SOLANA_VERSION}/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"

- name: Install Node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install MagicBlock validators (mb-test-validator + ephemeral-validator)
run: |
npm install -g "@magicblock-labs/ephemeral-validator@${EPHEMERAL_VALIDATOR_VERSION}"
mb-test-validator --version
ephemeral-validator --version

- uses: Swatinem/rust-cache@v2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚖️ Poor tradeoff

Consider pinning third-party actions to commit SHAs.

actions/checkout@v4, dtolnay/rust-toolchain@master, taiki-e/install-action@nextest, Swatinem/rust-cache@v2, and actions/setup-node@v4 are all tag/branch refs, which are mutable. Pinning to a commit SHA hardens against supply-chain tampering, at the cost of manual bumps (particularly painful for @master).

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 27-27: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 45-45: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 80-80: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 41-72: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 27-27: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 30-30: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 45-45: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 48-48: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 59-59: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 61-61: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 80-80: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 83-83: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 94-94: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 104-104: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[info] 30-30: action functionality is already included by the runner (superfluous-actions): use rustup and/or cargo in a script step

(superfluous-actions)


[info] 48-48: action functionality is already included by the runner (superfluous-actions): use rustup and/or cargo in a script step

(superfluous-actions)


[info] 83-83: action functionality is already included by the runner (superfluous-actions): use rustup and/or cargo in a script step

(superfluous-actions)


[warning] 100-100: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile

(adhoc-packages)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 27 - 104, The CI workflow still uses
mutable action refs, so update the workflows in ci.yml to pin each third-party
action to an immutable commit SHA instead of tags or branches. Replace the uses
entries for actions/checkout, dtolnay/rust-toolchain, taiki-e/install-action,
Swatinem/rust-cache, and actions/setup-node with their corresponding
commit-pinned versions, and keep the existing step structure and names
unchanged.

Source: Linters/SAST tools

Comment on lines 33 to +37
[dependencies]
ephemeral-rollups-pinocchio = { workspace = true }
pinocchio = { workspace = true }
solana-address = { workspace = true }
solana-define-syscall = { workspace = true, optional = true }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ephemeral-rollups-pinocchio is not feature-gated unlike its siblings.

solana-define-syscall and solana-program-error are both declared optional = true and only pulled in via the program/cpi-pinocchio features. ephemeral-rollups-pinocchio is added as a plain dependency, so it will always be compiled for every consumer of hydra-api (including pure base-program/client-only consumers), contradicting the feature-gating intent stated in crates/hydra-api/src/program/mod.rs's doc comment.

♻️ Suggested fix
-ephemeral-rollups-pinocchio = { workspace = true }
+ephemeral-rollups-pinocchio = { workspace = true, optional = true }

And add it to the relevant feature (e.g. cpi-pinocchio or a new ephemeral feature) alongside pinocchio/cpi.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/hydra-api/Cargo.toml` around lines 33 - 37,
`ephemeral-rollups-pinocchio` is currently always enabled in `hydra-api` instead
of being gated like the other optional dependencies. Update `Cargo.toml` so this
crate is declared optional and only activated through the appropriate feature in
the same way as `solana-define-syscall` and `solana-program-error`, then wire it
into the relevant feature set in `hydra-api` (for example the `cpi-pinocchio`
path or a dedicated `ephemeral` feature) to match the intent described by
`program/mod.rs`.

Comment on lines +16 to +25
//! ```ignore
//! use hydra_api::cpi::native as hydra_cpi;
//! use hydra_api::instruction::{CreateArgs, SchedMeta};
//!
//! // Inside your user-facing instruction's handler:
//! hydra_cpi::create(
//! payer_ai, crank_ai, system_program_ai,
//! &CreateArgs { seed, authority: [0u8; 32], /* ... */ },
//! )?;
//! ```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Stale doc example path and missing signer_seeds argument.

The example imports hydra_api::cpi::native, but after the base/ephemeral split the path is hydra_api::cpi::base::native. The create(...) call also omits the now-required signer_seeds parameter. Copy-pasting this doc won't compile.

📝 Suggested doc fix
-        //! use hydra_api::cpi::native as hydra_cpi;
+        //! use hydra_api::cpi::base::native as hydra_cpi;
         //! use hydra_api::instruction::{CreateArgs, SchedMeta};
         //!
         //! // Inside your user-facing instruction's handler:
         //! hydra_cpi::create(
         //!     payer_ai, crank_ai, system_program_ai,
         //!     &CreateArgs { seed, authority: [0u8; 32], /* ... */ },
+        //!     signer_seeds,
         //! )?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//! ```ignore
//! use hydra_api::cpi::native as hydra_cpi;
//! use hydra_api::instruction::{CreateArgs, SchedMeta};
//!
//! // Inside your user-facing instruction's handler:
//! hydra_cpi::create(
//! payer_ai, crank_ai, system_program_ai,
//! &CreateArgs { seed, authority: [0u8; 32], /* ... */ },
//! )?;
//! ```
//!
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/hydra-api/src/cpi.rs` around lines 16 - 25, Update the doc example
under hydra_api::cpi::native so it matches the current CPI API: change the
import path to hydra_api::cpi::base::native and adjust the hydra_cpi::create
example to include the required signer_seeds argument. Keep the example aligned
with the current create signature in cpi.rs so the snippet copy-pastes and
compiles.

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.

feat: cranks on ephemeral accounts

1 participant