Skip to content

test: submux dedup window expiry behavior#1100

Open
thlorenz wants to merge 2 commits intomasterfrom
thlorenz/dedupe-account-updates
Open

test: submux dedup window expiry behavior#1100
thlorenz wants to merge 2 commits intomasterfrom
thlorenz/dedupe-account-updates

Conversation

@thlorenz
Copy link
Copy Markdown
Collaborator

@thlorenz thlorenz commented Mar 27, 2026

Summary

Add test demonstrating dedup window expiry behavior in account updates. The test verifies that duplicate updates are suppressed within the dedup window and properly forwarded once the window expires.

Details

magicblock-chainlink

Added test_dedup_same_slot_after_window_expires() to verify the submux deduplication behavior respects its configured window duration:

  • First update of (pubkey, slot=42) is forwarded
  • Second identical update within the dedup window is suppressed
  • After the window expires (100ms), the same update is forwarded again

This ensures that the deduplication window expiry edge case is properly handled and doesn't inadvertently drop updates once the window expires.

Summary by CodeRabbit

  • Tests
    • Added test coverage for deduplication behavior with time-based window expiration.

Note: This release contains internal test improvements with no user-facing changes.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 27, 2026

Manual Deploy Available

You can trigger a manual deploy of this PR branch to testnet:

Deploy to Testnet 🚀

Alternative: Comment /deploy on this PR to trigger deployment directly.

⚠️ Note: Manual deploy requires authorization. Only authorized users can trigger deployments.

Comment updated automatically when the PR is synchronized.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b80323af-c434-43e7-9ec7-fc9026719482

📥 Commits

Reviewing files that changed from the base of the PR and between f8a5871 and 7586168.

📒 Files selected for processing (1)
  • magicblock-chainlink/src/submux/mod.rs

📝 Walkthrough

Walkthrough

A new test is added to verify the deduplication window behavior in SubMuxClient. The test creates a scenario where identical updates with the same pubkey and slot are sent from different clients, checking that the first update passes through, subsequent updates within the dedup window are suppressed, and updates received after the window expires are forwarded again. The test uses a 100ms dedup window and includes sleep periods to validate behavior across time boundaries.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch thlorenz/dedupe-account-updates

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
Copy link
Copy Markdown
Collaborator Author

thlorenz commented Mar 27, 2026

Analysis: Can Same-Slot Duplicates Escape?

Path 1: Two Forwarders, Same (pubkey, slot) — Sequential Lock

The processing path per forwarder is (mod.rs:616–639):

recv update → capture now → should_forward_dedup(shared_cache) → debounce → send

When two forwarders receive (pk, slot=X) at approximately the same time:

  1. Forwarder A acquires the dedup mutex. Key (pk, X) is not in the map.
    Inserts (pk, X) → now_A. Returns true. Releases lock.
  2. Forwarder B acquires the dedup mutex. Key (pk, X) is in the map.
    now_B - now_A is negligible (< dedup_window of 2000ms). Returns false.
    Releases lock. Update dropped.

The mutex serializes access. The first forwarder to acquire the lock wins;
all subsequent forwarders for the same (pubkey, slot) within the dedup window
are blocked. This is correct behavior.

Verdict: Same-slot duplicates from different forwarders cannot escape.

Path 2: Same Forwarder, Same (pubkey, slot) Sent Twice

If a single inner client delivers the same (pubkey, slot) twice (which should
not happen per the one-update-per-slot assumption, but defensively):

  1. First delivery: key inserted in shared cache, forwarded.
  2. Second delivery: key found in cache, now - ts < window, returns false.
    Update dropped.

Verdict: Cannot escape.

Path 3: Same (pubkey, slot) After Dedup Window Expires

If the same (pubkey, slot) arrives from a second client more than
dedup_window (default 2000ms) after the first:

  1. First delivery at t0: key inserted, forwarded.
  2. Second delivery at t0 + 2001ms: now - ts > window, key timestamp
    updated, returns true. Forwarded again.

This would produce a duplicate. However:

  • The default dedup window is 2000ms.
  • Solana slot time is ~400ms, so two RPC clients delivering the same slot
    update more than 2s apart would require an extreme network delay.
  • Even with network jitter, this scenario requires one client to be >2s behind.

Verdict: Theoretically possible under extreme network delay (>2s skew
between RPC clients for the same slot notification). Unlikely in practice
but worth noting.

Path 4: Dedup Window Pruner Race

The background pruner (mod.rs:499–514) runs every dedup_window and removes
entries where now - ts > window. Could pruning remove an entry just before
forwarder B checks it?

Timeline:

  1. t0: Forwarder A inserts (pk, X) → t0.
  2. t0 + window: Pruner runs. now - t0 = window, which is NOT > window.
    Entry retained.
  3. t0 + window + ε: Pruner could remove entry if it runs again. But the
    pruner sleeps for window between runs, so next prune is at t0 + 2*window.

For forwarder B to miss the entry, it would need to arrive > window after
forwarder A. This reduces to Path 3.

Verdict: The pruner does not create a new attack vector beyond Path 3.

Path 5: Clock Sysvar Bypass

Updates for the clock sysvar (clock::ID) bypass debounce entirely
(mod.rs:627–628) but still go through dedup. So same-slot clock updates from
different forwarders would still be deduped by the shared cache.

Verdict: Clock sysvar is protected by dedup. Cannot escape.


Why test_submux_dedup_identical_slot_updates Passes

The test at mod.rs:1116 sends (pk, slot=7) from two different clients and
asserts only one forwarded. It passes because the dedup cache is shared.
Both forwarders hit the same HashMap. The first to acquire the mutex inserts
the key; the second sees it and drops the update.

This test already covers the primary cross-forwarder same-slot scenario.


Conclusion

Same-slot duplicates cannot escape SubMuxClient under normal operating
conditions.
The dedup cache is shared across all forwarders via
Arc<Mutex<HashMap>>, and the mutex serializes all access. The only
theoretical escape path requires an RPC client to deliver a slot notification
more than 2 seconds after another client delivers the same one — an extreme
network delay scenario.

Root Cause of Duplicate Clones Must Be Elsewhere

Since SubMuxClient does not let same-slot duplicates through under normal
conditions, the reported duplicate clone transactions within <100ms must
originate from a different mechanism.


Can Duplicate Clone Transactions Occur if SubMuxClient Dedup Works?

Scope

Assuming SubMuxClient's dedup layer works correctly:

  • Same (pubkey, slot) pairs are deduplicated — only one forwarded
  • Different-slot updates for the same pubkey both clone (expected behavior)
  • FetchCloner never receives two identical (pubkey, slot) updates

Question: Under these conditions, can duplicate clone transactions for the same
(pubkey, slot) still occur?


Executive Summary

NO. If SubMuxClient dedup works correctly, duplicate clone transactions for the
same (pubkey, slot) are not possible.

Reasoning:

  1. SubMuxClient dedup suppresses same-slot duplicates
  2. FetchCloner receives at most one (pubkey, slot) pair
  3. FetchCloner spawns one task per unique update
  4. One update → one clone transaction
  5. Result: No duplicates

The Flow: Same-Slot Update Through Both Layers

SubMuxClient Dedup (Working Correctly)

RPC Clients deliver:
  t0:   (pubkey=PK, slot=100) from Client 1
        → Insert into dedup cache: (PK, 100) → t0
        → Forward to FetchCloner

  t10:  (pubkey=PK, slot=100) from Client 2
        → Check cache: (PK, 100) found at t0
        → now.duration_since(t0) = 10ms < dedup_window (2000ms)
        → DROP (deduped)
        → Do NOT forward to FetchCloner

FetchCloner receives:
  Only one update: (pubkey=PK, slot=100)

FetchCloner Processing

FetchCloner.start_subscription_listener receives:
  (pubkey=PK, slot=100)
  → spawn one task A

Task A:
  - Read bank: empty
  - Out-of-order check: bank_slot (none) >= update_slot (100)? No, proceed
  - Clone account with slot=100
  - One transaction submitted

Result:
  - One clone transaction for (PK, slot=100)
  - No duplicate

Conclusion

If SubMuxClient dedup works correctly, duplicate clone transactions for the same
(pubkey, slot) are impossible.

The dedup layer prevents FetchCloner from ever seeing the same (pubkey, slot)
twice. Each unique slot for a pubkey results in exactly one clone transaction.


Exception: Dedup Window Expiry

One edge case from 04_single-issue.md:

If the same (pubkey, slot) arrives more than dedup_window (default 2000ms)
after the first:

t0:     (PK, slot=100) from Client 1
        → Insert into cache: (PK, 100) → t0
        → Forward to FetchCloner

t2001:  (PK, slot=100) from Client 2
        → Check cache: (PK, 100) found at t0
        → now.duration_since(t0) = 2001ms > dedup_window (2000ms)
        → Update cache: (PK, 100) → t2001
        → Forward to FetchCloner (window expired!)

FetchCloner receives:
  (PK, slot=100) at t0  → clone transaction A
  (PK, slot=100) at t2001 → clone transaction B

Result:
  Two clone transactions for same (PK, slot=100) with 2+ second gap

Likelihood: Extremely low in normal operation. Would require:

  • Two RPC clients delivering the same slot notification >2 seconds apart
  • Solana slot time is ~400ms, so this requires one client being >2s behind
  • Possible only under extreme network delay or network partition

Mitigation: The 2s default window provides generous headroom. For production,
this is negligible risk.


Files Involved

@thlorenz thlorenz marked this pull request as ready for review March 27, 2026 10:34
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