Skip to content

refactor: move commit-and-undelegate scheduling service from magicblock-accounts to magicblock-services#1402

Closed
snawaz wants to merge 1 commit into
snawaz/backfill-reqfrom
snawaz/refactor
Closed

refactor: move commit-and-undelegate scheduling service from magicblock-accounts to magicblock-services#1402
snawaz wants to merge 1 commit into
snawaz/backfill-reqfrom
snawaz/refactor

Conversation

@snawaz

@snawaz snawaz commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Breaking Changes

  • None
  • Yes — migration path described below

Test Plan

Summary by CodeRabbit

  • New Features

    • Added background handling for DLP undelegation requests and related scheduling during validator startup.
    • Updated validator flow to process scheduled intents directly, improving how commit and undelegation work is coordinated.
  • Bug Fixes

    • Fixed ownership and routing of undelegation and commit processing so the correct service handles each step.
    • Removed outdated references to the legacy account-processing path across docs and configuration.

snawaz commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR removes the magicblock-accounts crate entirely from the workspace, deleting its source files, Cargo manifest, and README. Its scheduled-commit and undelegation-request processing responsibilities are replaced by a new UndelegationRequestService added to magicblock-services, which handles DLP undelegation request subscription/polling and schedules local ScheduleCommitAndUndelegate transactions. magicblock-api's MagicValidator is rewired to construct, start, and stop this new service instead of ScheduledCommitsProcessorImpl, and the ApiError::AccountsError variant/conversion is removed. Extensive documentation across architecture, crate-map, per-crate guides, and validator specification files is updated to reflect the new ownership boundaries and IntentExecutionService/UndelegationRequestService terminology. Manual test scripts' logging configuration is also updated.

Sequence Diagram(s)

sequenceDiagram
  participant MagicValidator
  participant UndelegationRequestService
  participant Chainlink
  participant TransactionScheduler

  MagicValidator->>UndelegationRequestService: new(chainlink, scheduler, poll_interval, ...)
  MagicValidator->>UndelegationRequestService: start()
  UndelegationRequestService->>Chainlink: subscribe_undelegation_requests()
  alt subscription succeeds
    UndelegationRequestService->>UndelegationRequestService: spawn subscription_processor
    UndelegationRequestService->>UndelegationRequestService: spawn poll_processor (if poll_interval > 0)
  else subscription fails
    UndelegationRequestService->>UndelegationRequestService: spawn poll_processor only
  end
  UndelegationRequestService->>Chainlink: observe DLP UndelegationRequest accounts
  UndelegationRequestService->>TransactionScheduler: schedule ScheduleCommitAndUndelegate
  MagicValidator->>UndelegationRequestService: stop()
Loading

Suggested reviewers: GabrielePicco, bmuddha

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch snawaz/refactor

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.

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
magicblock-services/src/undelegation_request_service.rs (1)

31-40: 🎯 Functional Correctness | 🔵 Trivial

All Schedule errors treated as retryable regardless of underlying TransactionError kind.

retryable() unconditionally returns true for Schedule(TransactionError). This means permanent failures (e.g. malformed instruction, insufficient funds, invalid program) get retried up to UNDELEGATION_REQUEST_MAX_ATTEMPTS just like transient ones, wasting retry budget and delaying the terminal error log. Consider inspecting the TransactionError variant to distinguish retryable (e.g. blockhash-not-found, node-busy) from permanent errors.

🤖 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 `@magicblock-services/src/undelegation_request_service.rs` around lines 31 -
40, The retryable() logic in ObservedUndelegationRequestError currently treats
every Schedule(TransactionError) as retryable, which causes permanent
transaction failures to be retried unnecessarily. Update retryable() to inspect
the underlying TransactionError variant and return true only for genuinely
transient cases (for example, blockhash-related or node-busy errors) while
returning false for permanent failures. Use ObservedUndelegationRequestError and
its retryable() method to locate the change.
🤖 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 `@magicblock-services/src/undelegation_request_service.rs`:
- Around line 364-374: The fallback path in start() can become completely inert
when Chainlink subscription fails and poll_interval is zero, because the current
warn! is skipped and spawn_poll_processor() only emits debug output. Update the
start() logic in UndelegationRequestService to detect the specific combination
of subscribe_undelegation_requests() returning None and polling being disabled,
and emit a warn! or error! there before returning or attempting a no-op fallback
so operators can see that undelegation processing is effectively disabled.

---

Outside diff comments:
In `@magicblock-services/src/undelegation_request_service.rs`:
- Around line 31-40: The retryable() logic in ObservedUndelegationRequestError
currently treats every Schedule(TransactionError) as retryable, which causes
permanent transaction failures to be retried unnecessarily. Update retryable()
to inspect the underlying TransactionError variant and return true only for
genuinely transient cases (for example, blockhash-related or node-busy errors)
while returning false for permanent failures. Use
ObservedUndelegationRequestError and its retryable() method to locate the
change.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: afa8b050-8fff-49b3-9e7f-31f5184b4eab

📥 Commits

Reviewing files that changed from the base of the PR and between e8006e0 and 50b5fcb.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • .agents/context/architecture.md
  • .agents/context/crate-map.md
  • .agents/context/crates/magicblock-account-cloner.md
  • .agents/context/crates/magicblock-accounts.md
  • .agents/context/crates/magicblock-api.md
  • .agents/context/crates/magicblock-chainlink.md
  • .agents/context/crates/magicblock-committor-program.md
  • .agents/context/crates/magicblock-committor-service.md
  • .agents/context/crates/magicblock-config.md
  • .agents/context/crates/magicblock-core.md
  • .agents/context/crates/magicblock-magic-program-api.md
  • .agents/context/crates/magicblock-metrics.md
  • .agents/context/crates/magicblock-services.md
  • .agents/specs/validator-specification.md
  • Cargo.toml
  • docs/architecture.md
  • magicblock-accounts/Cargo.toml
  • magicblock-accounts/README.md
  • magicblock-accounts/src/config.rs
  • magicblock-accounts/src/errors.rs
  • magicblock-accounts/src/lib.rs
  • magicblock-accounts/src/traits.rs
  • magicblock-api/Cargo.toml
  • magicblock-api/src/errors.rs
  • magicblock-api/src/magic_validator.rs
  • magicblock-committor-service/README.md
  • magicblock-services/Cargo.toml
  • magicblock-services/src/lib.rs
  • magicblock-services/src/undelegation_request_service.rs
  • test-manual/Makefile
  • test-manual/helius-laser/sh/04_start-validator.sh
💤 Files with no reviewable changes (9)
  • magicblock-accounts/README.md
  • magicblock-accounts/Cargo.toml
  • magicblock-accounts/src/traits.rs
  • Cargo.toml
  • magicblock-accounts/src/lib.rs
  • magicblock-accounts/src/config.rs
  • magicblock-accounts/src/errors.rs
  • magicblock-api/Cargo.toml
  • magicblock-api/src/errors.rs

Comment on lines +364 to 374
pub fn start(self: &Arc<Self>) {
let Some(requests) = self.chainlink.subscribe_undelegation_requests()
else {
if !self.undelegation_request_poll_interval.is_zero() {
if !self.poll_interval.is_zero() {
warn!(
"Cannot subscribe to DLP undelegation requests; falling back to polling only"
);
}
self.spawn_undelegation_request_poll_processor();
self.spawn_poll_processor();
return;
};

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Silent full disablement when subscription fails and polling is disabled by config.

If subscribe_undelegation_requests() returns None and poll_interval is zero (explicitly disabled), the warn! is skipped (only fires if !self.poll_interval.is_zero()), and spawn_poll_processor only logs at debug!. The net effect: undelegation requests are never processed, with no operator-visible signal above debug level.

Consider logging a warn!/error! in this specific combination (subscribe failed AND polling disabled) since the service is effectively fully inert.

🔧 Proposed fix
     pub fn start(self: &Arc<Self>) {
         let Some(requests) = self.chainlink.subscribe_undelegation_requests()
         else {
-            if !self.poll_interval.is_zero() {
-                warn!(
-                    "Cannot subscribe to DLP undelegation requests; falling back to polling only"
-                );
-            }
+            if self.poll_interval.is_zero() {
+                error!(
+                    "Cannot subscribe to DLP undelegation requests and polling is disabled; undelegation requests will not be processed"
+                );
+            } else {
+                warn!(
+                    "Cannot subscribe to DLP undelegation requests; falling back to polling only"
+                );
+            }
             self.spawn_poll_processor();
             return;
         };
📝 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
pub fn start(self: &Arc<Self>) {
let Some(requests) = self.chainlink.subscribe_undelegation_requests()
else {
if !self.undelegation_request_poll_interval.is_zero() {
if !self.poll_interval.is_zero() {
warn!(
"Cannot subscribe to DLP undelegation requests; falling back to polling only"
);
}
self.spawn_undelegation_request_poll_processor();
self.spawn_poll_processor();
return;
};
pub fn start(self: &Arc<Self>) {
let Some(requests) = self.chainlink.subscribe_undelegation_requests()
else {
if self.poll_interval.is_zero() {
error!(
"Cannot subscribe to DLP undelegation requests and polling is disabled; undelegation requests will not be processed"
);
} else {
warn!(
"Cannot subscribe to DLP undelegation requests; falling back to polling only"
);
}
self.spawn_poll_processor();
return;
};
🤖 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 `@magicblock-services/src/undelegation_request_service.rs` around lines 364 -
374, The fallback path in start() can become completely inert when Chainlink
subscription fails and poll_interval is zero, because the current warn! is
skipped and spawn_poll_processor() only emits debug output. Update the start()
logic in UndelegationRequestService to detect the specific combination of
subscribe_undelegation_requests() returning None and polling being disabled, and
emit a warn! or error! there before returning or attempting a no-op fallback so
operators can see that undelegation processing is effectively disabled.

@snawaz

snawaz commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

discarded, as it should be done differently.

@snawaz snawaz closed this Jul 7, 2026
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