DEV Community

ohmygod
ohmygod

Posted on

Solana's Noisy Neighbor Attack: How Localized Fee Markets Let Attackers Block Your DeFi Liquidations — And the Detection Toolkit to Stop Them

Solana's Noisy Neighbor Attack: How Localized Fee Markets Let Attackers Block Your DeFi Liquidations

TL;DR: Solana's Localized Fee Markets (LFM) solved global congestion — but introduced a surgical denial-of-service vector. By flooding write-locks on a single protocol's state accounts, an attacker can price out keeper bots during the exact moments liquidations matter most. We break down the attack mechanics, show real detection patterns, and provide a state-sharding migration guide.


The Promise That Became a Weapon

When Solana introduced Localized Fee Markets via SIMD-0110, it was hailed as an elegant solution to network-wide congestion. Instead of every transaction competing in a single global fee auction, fees became localized — you only paid premium rates when contending for the same state accounts as other transactions.

The theory was sound: a spike in NFT minting shouldn't make your DeFi swap expensive. Each "hot" account gets its own micro-market.

But here's the thing about micro-markets: they can be cornered.

Anatomy of the Noisy Neighbor Attack

The Setup

Consider a typical Solana DeFi lending protocol — let's call it LendProtocol. Like most Solana programs, it uses a handful of global state accounts:

GlobalState PDA        → Tracks total deposits, borrows, utilization
MarketReserve PDA      → Per-asset reserve data
OracleBuffer PDA       → Cached price feeds
LiquidationQueue PDA   → Pending liquidation orders
Enter fullscreen mode Exit fullscreen mode

Every liquidation, deposit, borrow, and repay instruction needs a write lock on at least GlobalState and MarketReserve. This means all these transactions are competing in the same Localized Fee Market.

The Attack

  1. Reconnaissance: The attacker maps the protocol's write-lock dependency graph. They identify the 2-3 PDAs that appear as writable accounts in >80% of the protocol's transactions.

  2. Trigger Detection: The attacker monitors CEX prices (via websocket feeds to Binance, OKX, etc.) for volatile moves that will trigger on-chain liquidations once oracle prices update.

  3. Fee Floor Flooding: When a liquidation-triggering price move is detected, the attacker submits hundreds of no-op transactions that request write locks on the protocol's global state PDAs:

// Attacker's "noisy" program
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _data: &[u8],
) -> ProgramResult {
    // Request write lock on target PDAs
    // Do nothing. Just occupy the fee market.
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Each transaction includes a high priority fee — but the attacker doesn't need to outbid everyone forever. They just need to push the localized base fee high enough that keeper bots' pre-configured fee caps are exceeded.

  1. Liquidation Window Closes: With keepers priced out, the protocol's underwater positions remain unliquidated through the volatile period. The attacker, who holds a leveraged position that would have been liquidated, survives. Or worse — they're simultaneously shorting the protocol's governance token, profiting from the insolvency event.

Cost Analysis

This is the terrifying part. Because Localized Fee Markets only affect transactions touching the same state accounts, the attacker's cost is dramatically lower than a global DoS:

  • Global Solana DoS (pre-LFM): ~$50,000+/min, affects everything
  • Localized Fee Market DoS: ~$200-2,000/min, targets single protocol
  • Ethereum gas price manipulation: ~$10,000+/min, affects everyone in same block

A few thousand dollars per minute to selectively freeze a protocol holding $100M+ in user funds. The economics are heavily in the attacker's favor.

The Firedancer Amplification

The 2026 rollout of Firedancer makes this worse. Under the Alpenglow consensus model with SIMD-0370's dynamic block sizing, high-performance leaders can produce blocks with hundreds of millions of Compute Units. This means:

  • More transaction slots for the attacker to fill with noise
  • Higher throughput means more write-lock contention can be generated per slot
  • Skip-vote dynamics create finality uncertainty that extends the attack window

A liquidation bot that waits for Finalized commitment (the safe choice) now faces 2-3x longer confirmation times during periods of validator heterogeneity. Combined with LFM DoS, the keeper is both priced out and unsure when their transaction will actually land.

Detection: How to Spot It Happening

Pattern 1: Fee Spike Isolation

Monitor your protocol's localized base fee independently from the global base fee:

# Pseudo-code for LFM anomaly detection
def detect_noisy_neighbor(protocol_accounts, window_slots=10):
    local_fee = get_localized_base_fee(protocol_accounts, window_slots)
    global_fee = get_global_base_fee(window_slots)

    ratio = local_fee / max(global_fee, 1)

    if ratio > 5.0:  # Local fee 5x+ above global
        alert(f"LFM anomaly: local/global ratio = {ratio:.1f}")

    # Check if spike correlates with oracle volatility
    oracle_vol = get_price_volatility(window_slots)
    if ratio > 3.0 and oracle_vol > THRESHOLD:
        alert("CRITICAL: Possible Noisy Neighbor during volatility event")
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Write-Lock Concentration

Track unique signers requesting write-locks on your PDAs:

def detect_write_lock_concentration(pda_pubkey, window_slots=5):
    txns = get_recent_write_lock_txns(pda_pubkey, window_slots)

    signer_counts = Counter(tx.signer for tx in txns)
    total = len(txns)

    # If top 3 signers account for >60% of write locks
    top3_share = sum(c for _, c in signer_counts.most_common(3)) / total

    if top3_share > 0.6:
        alert(f"Write-lock concentration: top 3 signers = {top3_share:.0%}")
Enter fullscreen mode Exit fullscreen mode

Pattern 3: No-Op Transaction Fingerprinting

Attacker transactions have a distinctive profile — they request write locks but perform minimal computation:

  • CU consumed << CU requested: The transaction requests maximum CUs but uses <5,000
  • No meaningful state changes: The writable accounts are not actually modified
  • Repetitive instruction data: Same program, same accounts, same (empty) data across hundreds of transactions

Mitigation: The State-Sharding Playbook

The fundamental fix is reducing write-lock contention on global state. Here's the migration path:

Step 1: Identify Hot Accounts

# Using Helius API to map write-lock hotspots
curl -s "https://api.helius.xyz/v0/addresses/{PROGRAM_ID}/transactions?api-key=KEY&limit=1000" \
  | jq '[.[] | .accountData[] | select(.writable == true) | .account] | group_by(.) | map({account: .[0], count: length}) | sort_by(-.count) | .[0:5]'
Enter fullscreen mode Exit fullscreen mode

Any account appearing as writable in >10% of your traffic is a high-severity LDoS target.

Step 2: Shard Global State

Replace single-PDA global state with user-derived shards:

// BEFORE: Single global state (LDoS vulnerable)
#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut, seeds = [b"global_state"], bump)]
    pub global_state: Account<'info, GlobalState>,
    // ...
}

// AFTER: Sharded state (LDoS resistant)  
#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(
        mut,
        seeds = [b"user_shard", user.key().as_ref()],
        bump
    )]
    pub user_shard: Account<'info, UserShard>,
    // Global aggregation happens asynchronously via cranks
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement Priority Fee Escalation in Keepers

Keeper bots must dynamically respond to localized fee spikes:

async def submit_liquidation(tx, protocol_accounts):
    base_fee = await get_localized_fee(protocol_accounts)

    # Exponential escalation with economic ceiling
    max_profitable_fee = calculate_liquidation_profit(tx) * 0.8

    for attempt in range(MAX_ATTEMPTS):
        priority_fee = min(
            base_fee * (1.5 ** attempt),
            max_profitable_fee
        )

        result = await send_transaction(tx, priority_fee)
        if result.confirmed:
            return result

    # If all attempts fail, trigger protocol-level circuit breaker
    await trigger_emergency_pause(reason="keeper_priced_out")
Enter fullscreen mode Exit fullscreen mode

Step 4: Protocol-Level Circuit Breakers

If liquidations can't execute for N consecutive slots, the protocol should automatically:

  1. Pause new borrows (prevent further bad debt accumulation)
  2. Widen liquidation bonus (incentivize liquidators to bid higher)
  3. Activate emergency oracle mode (switch to TWAP to reduce manipulation surface)
pub fn check_liquidation_health(ctx: Context<CrankCheck>) -> Result<()> {
    let state = &mut ctx.accounts.protocol_state;
    let current_slot = Clock::get()?.slot;

    if current_slot - state.last_successful_liquidation_slot > STALE_THRESHOLD {
        state.emergency_mode = true;
        state.borrow_paused = true;
        state.liquidation_bonus_bps += EMERGENCY_BONUS_INCREMENT;

        emit!(EmergencyModeActivated {
            slot: current_slot,
            stale_slots: current_slot - state.last_successful_liquidation_slot,
        });
    }

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

The Bigger Picture: Why This Matters Now

Q1 2026 has already seen ~$137M in DeFi exploits. The Step Finance incident ($27M via compromised keys), the Resolv stablecoin exploit ($25M via unbacked minting), and the CrossCurve bridge drain ($3M) all targeted different vectors — but the Noisy Neighbor attack combines economic incentive alignment with low barrier to entry in a way none of these did.

The attacker doesn't need:

  • ❌ A smart contract bug to exploit
  • ❌ A compromised private key
  • ❌ A flash loan for capital
  • ❌ Complex cross-chain bridging

They just need:

  • ✅ SOL for transaction fees (~$2,000 in the worst case)
  • ✅ A leveraged position they want to protect
  • ✅ Knowledge of the target protocol's state account layout

This is not theoretical. The write-lock dependency graphs of the top 20 Solana DeFi protocols are public. Every account a program touches is visible on-chain. The only question is when — not if — we see this exploited at scale.

Checklist: Is Your Protocol Vulnerable?

  • Global state PDAs: Sharded by user/market ✅ vs Single PDA for all operations ❌
  • Write-lock concentration: <10% of traffic on any single account ✅ vs >10% on 1+ accounts ❌
  • Keeper fee strategy: Dynamic escalation with profit ceiling ✅ vs Static priority fee ❌
  • Circuit breaker: Auto-pause after N stale slots ✅ vs None ❌
  • LFM monitoring: Real-time local/global fee ratio alerts ✅ vs No monitoring ❌
  • Liquidation commitment: Configurable (Confirmed → Finalized) ✅ vs Hardcoded ❌

If you checked ❌ on 3+ items, your protocol is a Noisy Neighbor target today.


References


This article is part of the DreamWork Security research series on emerging blockchain attack vectors. Follow for weekly deep dives into DeFi security, audit tooling, and vulnerability analysis.

Top comments (0)