Skip to content

fix: drop stale subscription updates#1264

Open
thlorenz wants to merge 11 commits into
masterfrom
thlorenz/fix-recreate-evicted-accounts
Open

fix: drop stale subscription updates#1264
thlorenz wants to merge 11 commits into
masterfrom
thlorenz/fix-recreate-evicted-accounts

Conversation

@thlorenz
Copy link
Copy Markdown
Collaborator

@thlorenz thlorenz commented May 29, 2026

Summary

Prevent stale forwarded subscription updates from recreating local bank state for accounts that are no longer watched by the remote account provider.

Details

magicblock-chainlink

  • Drop subscription updates in the fetch cloner when the provider no longer watches the account, while preserving the existing delegated-account discovery path.
  • If a stale update arrives for an account that is still present locally, route cleanup through the existing account-removal listener instead of evicting directly from the subscription-update task.
  • Serialize the final watched-account check and eviction submission for the same pubkey so a concurrent re-subscription cannot race with stale-account cleanup.
  • Keep unsubscribe behavior and the SubMux forwarder hot path unchanged.
  • Add regression coverage for stale updates, removal enqueueing, watched-again skip behavior, and same-pubkey eviction/subscription serialization.

Summary by CodeRabbit

  • New Features

    • Subscription updates now carry an explicit source (account vs program), improving update routing and handling.
  • Bug Fixes

    • Stop processing stale forwarded subscription updates for unwatched accounts. If the account still exists, a best-effort removal notification is enqueued (failures logged) and eviction is gated to avoid races with re-watching.
  • Tests

    • Added tests and helpers validating forwarded-update handling, removal-notification enqueueing, guarded eviction with per-key locking, and re-watch race scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a crate-visible RemoteAccountProvider accessor on FetchCloner and drops stale forwarded account updates when the provider no longer watches the pubkey; if the account still exists, the cloner enqueues a best-effort removal update. Introduces RemoteAccountProvider::evict_unwatched_with_subscription_lock, wires it into Chainlink's subscribe_account_removals to avoid eviction/subscription races, propagates SubscriptionSource through pubsub plumbing, and updates/extends tests with provider helpers and new cases exercising eviction coordination and unwatched-update behavior.

Suggested reviewers

  • GabrielePicco
  • snawaz
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch thlorenz/fix-recreate-evicted-accounts

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 and usage tips.

@thlorenz thlorenz requested a review from GabrielePicco May 29, 2026 11:19
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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-chainlink/src/chainlink/fetch_cloner/tests.rs`:
- Around line 1666-1673: The test currently breaks out immediately because
clone_request_count() can be 0 at baseline; change the test to first observe
that a clone request was created (wait for
ctx.fetch_cloner.cloner().clone_request_count() to become > 0 or use a
deterministic post-send signal from the code path) then wait for it to drop back
to 0; in other words, gate the zero-check behind a confirmed prior increment (or
await a dedicated processing-complete signal/oneshot you add to the async drop
path) so the test actually exercises the async drop processing in tests.rs where
ctx.fetch_cloner.cloner() and clone_request_count() are used.
🪄 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: 6fd6c77b-e2fa-466c-a66d-497b39471bb0

📥 Commits

Reviewing files that changed from the base of the PR and between a49abaa and 7e89661.

📒 Files selected for processing (4)
  • magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs
  • magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs
  • magicblock-chainlink/src/chainlink/mod.rs
  • magicblock-chainlink/src/remote_account_provider/mod.rs

Comment thread magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs
@thlorenz thlorenz marked this pull request as ready for review June 1, 2026 07:50
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs (1)

597-616: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't enqueue removal for intentionally unwatched delegated accounts.

A delegated account can legitimately still be in the bank after its DirectAccount subscription was released. In that case, a late forwarded update will now hit this !is_watching branch and enqueue removal anyway, which lets the removal listener evict authoritative local delegated state. Please exempt the same local state that later triggers cleanup_direct_subscription_for_delegated_account(...) here as well, and add a regression test for the late-update case.

Suggested guard
         let update_slot = update.account.slot();
         if !self.remote_account_provider.is_watching(&pubkey) {
+            if self.accounts_bank.get_account(&pubkey).is_some_and(|in_bank| {
+                in_bank.delegated() && !in_bank.undelegating()
+            }) {
+                trace!(
+                    pubkey = %pubkey,
+                    update_slot,
+                    "Dropping late update for intentionally unwatched delegated account"
+                );
+                return;
+            }
             trace!(
                 pubkey = %pubkey,
                 update_slot,
                 "Dropping subscription update for account that is no longer watched"
             );
🤖 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-chainlink/src/chainlink/fetch_cloner/mod.rs` around lines 597 -
616, When handling the !self.remote_account_provider.is_watching(&pubkey)
branch, avoid enqueueing a removal for accounts that were intentionally
unwatched because they are delegated; add a guard that detects the same
condition used by cleanup_direct_subscription_for_delegated_account(...) (e.g.,
consult the delegated subscription marker/state or invoke the same
predicate/function) and skip calling
self.remote_account_provider.send_removal_update(pubkey). Update the logic
around accounts_bank.get_account(&pubkey) to short-circuit when that
delegated-unwatched predicate is true, and add a regression test that simulates
a late forwarded update after a delegated DirectAccount subscription was
released to ensure no removal is enqueued.
🤖 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.

Outside diff comments:
In `@magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs`:
- Around line 597-616: When handling the
!self.remote_account_provider.is_watching(&pubkey) branch, avoid enqueueing a
removal for accounts that were intentionally unwatched because they are
delegated; add a guard that detects the same condition used by
cleanup_direct_subscription_for_delegated_account(...) (e.g., consult the
delegated subscription marker/state or invoke the same predicate/function) and
skip calling self.remote_account_provider.send_removal_update(pubkey). Update
the logic around accounts_bank.get_account(&pubkey) to short-circuit when that
delegated-unwatched predicate is true, and add a regression test that simulates
a late forwarded update after a delegated DirectAccount subscription was
released to ensure no removal is enqueued.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9595bd59-99c2-4f53-a31b-c44b105c1927

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad915b and 2b8901d.

📒 Files selected for processing (4)
  • magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs
  • magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs
  • magicblock-chainlink/src/chainlink/mod.rs
  • magicblock-chainlink/src/remote_account_provider/mod.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs (1)

4524-4531: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Acquire DirectAccount before these non-delegated forwarded updates

Both tests now send SubscriptionSource::Account updates for plain accounts that are not being watched. Under the new stale-update filter, those updates are dropped, so the assertions can still pass without ever exercising the intended subscription-owner path. Line 4524 and Line 4754 should establish a direct subscription first, like the other updated subscription tests do.

Suggested fix
     let (subscription_tx, subscription_rx) = mpsc::channel(100);
     let fetch_cloner = FetchCloner::new(
         &remote_account_provider,
         &accounts_bank,
         &cloner_stub,
         validator_keypair.insecure_clone(),
         subscription_rx,
         None,
         None,
     );

+    acquire_direct_subscription_for_update(
+        &remote_account_provider,
+        &account_pubkey,
+    )
+    .await;
+
     // Send subscription update (this will become the owner).
     let subscription_account =
         crate::remote_account_provider::RemoteAccount::from_fresh_account(
             account.clone(),
             CURRENT_SLOT,
@@
     let (subscription_tx, subscription_rx) = mpsc::channel(100);
     let fetch_cloner = FetchCloner::new(
         &remote_account_provider,
         &accounts_bank,
         &cloner_stub,
         validator_keypair.insecure_clone(),
         subscription_rx,
         None,
         None,
     );

+    acquire_direct_subscription_for_update(
+        &remote_account_provider,
+        &account_pubkey,
+    )
+    .await;
+
     // Send subscription update (becomes owner, will fail).
     let subscription_account =
         crate::remote_account_provider::RemoteAccount::from_fresh_account(
             account.clone(),
             CURRENT_SLOT,

Also applies to: 4754-4761

🤖 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-chainlink/src/chainlink/fetch_cloner/tests.rs` around lines 4524 -
4531, The test sends non-delegated ForwardedSubscriptionUpdate messages
(subscription_tx + ForwardedSubscriptionUpdate with SubscriptionSource::Account)
before ever establishing a direct subscription, so those updates get dropped by
the stale-update filter; fix by first acquiring a DirectAccount subscription for
the target pubkey the same way other updated-subscription tests do—i.e., before
the lines that send the non-delegated updates, send the helper/direct
subscription message (the ForwardedSubscriptionUpdate that creates a
DirectAccount subscription or invoke the test helper used elsewhere to subscribe
the account) so the subsequent SubscriptionSource::Account forwarded updates
exercise the subscription-owner path.
🤖 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.

Outside diff comments:
In `@magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs`:
- Around line 4524-4531: The test sends non-delegated
ForwardedSubscriptionUpdate messages (subscription_tx +
ForwardedSubscriptionUpdate with SubscriptionSource::Account) before ever
establishing a direct subscription, so those updates get dropped by the
stale-update filter; fix by first acquiring a DirectAccount subscription for the
target pubkey the same way other updated-subscription tests do—i.e., before the
lines that send the non-delegated updates, send the helper/direct subscription
message (the ForwardedSubscriptionUpdate that creates a DirectAccount
subscription or invoke the test helper used elsewhere to subscribe the account)
so the subsequent SubscriptionSource::Account forwarded updates exercise the
subscription-owner path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 443cca9e-7dd1-4e9a-9e2e-b7d11dbb667f

📥 Commits

Reviewing files that changed from the base of the PR and between fc8801f and 07fce06.

📒 Files selected for processing (7)
  • magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs
  • magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs
  • magicblock-chainlink/src/remote_account_provider/chain_laser_actor/actor.rs
  • magicblock-chainlink/src/remote_account_provider/chain_pubsub_actor.rs
  • magicblock-chainlink/src/remote_account_provider/chain_pubsub_client.rs
  • magicblock-chainlink/src/remote_account_provider/mod.rs
  • magicblock-chainlink/src/remote_account_provider/pubsub_common.rs

* master:
  fix: reject short account responses (#1290)
  fix: wait for pubsub listeners before reconnect (#1253)
  fix: retry failed program subscriptions (#1268)
  release: 0.12.0 (#1299)
  Ignore compute unit price in processor fees (#1298)
  Recover recent pending intents on restart (#1296)
  chore: adjust log level (#1297)
  release: v0.11.4 (#1292)
  fix(scheduler): remove block subscription in the scheduler (#1293)
  fix(committor): race-condition on cleanup (#1291)
  Handle ALT extend invalid instruction data (#1287)
  fix: use provided compute limits instead of defaults (#1289)
  feat: snapshot accountsdb even in the replica mode (#1282)
  feat: added vrf ephemeral test queue, delegation record and metadata for mb-test-validator (#1281)
  fix: use wire size (1232), not encoded size (1644), for tx fit checks! (#1285)
  chore: simplify, rename out_of_order_slot and add a comment (#1284)
  fix: execute post-delegation actions after clone (#1278)
  Handle oversized single-stage committor transactions (#1277)
  Reduce committor RPC confirmation calls (#1271)
  fix: preserve streams on optimize failure (#1273)
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