DEV Community

ohmygod
ohmygod

Posted on

Localized DoS on Solana: How Attackers Weaponize Fee Markets to Grief Individual Protocols for Pennies

Localized DoS on Solana: How Attackers Weaponize Fee Markets to Grief Individual Protocols for Pennies

Solana's localized fee markets were supposed to be an upgrade — isolate congestion so one hot mint doesn't spike fees globally. But this same isolation creates an attack primitive that lets anyone grief a specific protocol at a fraction of what a traditional DoS would cost.

This isn't theoretical. Multiple DeFi protocols on Solana have experienced suspicious fee spikes in Q1 2026 that match the Localized DoS (LDoS) pattern. Let's break down how it works, why it's cheap, and what defenders can build today.

How Solana's Localized Fee Markets Work

Since the introduction of SIMD-0110 and subsequent improvements, Solana uses per-account fee markets. When you submit a transaction, the priority fee you pay is determined by contention on the specific accounts your transaction touches — not global network load.

This means:

  • A hot NFT mint drives up fees only for transactions touching that mint's accounts
  • DEX swaps on Pool A have independent fee dynamics from Pool B
  • Validators use a scheduler that groups transactions by account write-locks

This is elegant. It's also exploitable.

The LDoS Attack Pattern

The attack is disarmingly simple:

  1. Identify the target protocol's hot accounts — the state accounts that every user transaction must write to (pool state, vault, oracle buffer, etc.)
  2. Spam cheap transactions that write-lock those same accounts with high priority fees
  3. The scheduler groups your spam with legitimate user transactions, forcing users to outbid your artificially inflated priority fees
  4. Result: Users face 10-100x normal fees for that specific protocol. Many give up. Liquidity migrates.
// Attacker's perspective (pseudocode)
const TARGET_ACCOUNTS = [
  poolState,      // Protocol's main state PDA
  vaultTokenA,    // Token vault A
  vaultTokenB,    // Token vault B
];

// Spam loop: ~50 txs/second, each touching target accounts
for (let i = 0; i < SPAM_RATE; i++) {
  const tx = new Transaction().add(
    // Minimal instruction that write-locks target accounts
    // but does nothing meaningful
    createNoopWriteLock(TARGET_ACCOUNTS)
  );
  tx.recentBlockhash = latestBlockhash;
  tx.add(ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: SPIKE_FEE  // e.g., 100,000 microlamports
  }));
  sendTransaction(tx);
}
Enter fullscreen mode Exit fullscreen mode

Why It's Cheap

The attacker's transactions don't need to succeed. They just need to enter the scheduler's queue and claim write-locks long enough to spike the local fee market.

Cost breakdown for a sustained 10-minute attack:

  • Transaction count: ~30,000 (50/sec × 600 sec)
  • Base fee per tx: 5,000 lamports (0.000005 SOL)
  • Priority fee per tx: ~100,000 microlamports/CU × 200 CUs = 0.00002 SOL
  • Total cost: ~0.75 SOL (~$100-150 at current prices)

For $150, you can make a protocol nearly unusable for 10 minutes. For a liquidation-sensitive lending protocol, that's enough to cause cascading liquidation failures.

Real-World Impact Scenarios

Scenario 1: Liquidation Griefing

An attacker holds an underwater position on a Solana lending protocol. Instead of getting liquidated, they LDoS the protocol's accounts. Liquidation bots can't land transactions at reasonable cost. The position survives long enough for the price to recover. Cost of attack: $500. Value saved from liquidation: $50,000+.

Scenario 2: Competitive DEX Warfare

Protocol A wants to steal volume from Protocol B. By LDoS-ing Protocol B's pool accounts during peak trading hours, users experience failed or expensive swaps and migrate to Protocol A. Plausible deniability is built in — fee spikes happen \"naturally\" during high volume.

Scenario 3: Oracle Manipulation Amplifier

An attacker combines LDoS with oracle manipulation. First, manipulate a price feed. Then LDoS the protocol that depends on that feed, preventing arbitrageurs from correcting the price. The stale-price window extends from seconds to minutes.

Why Existing Defenses Fall Short

Priority Fee Estimation Won't Save You

Most Solana wallets and SDKs estimate priority fees based on recent slot data. During an LDoS attack, the estimator sees inflated fees and recommends users pay them — effectively passing the attacker's cost to users.

Rate Limiting at the RPC Level Is Insufficient

Attackers can distribute transactions across hundreds of RPC endpoints or run their own validator/RPC. Network-level rate limiting doesn't address the fundamental issue: the scheduler treats attacker and victim transactions identically.

\"Just Use More Accounts\" Doesn't Scale

Some suggest protocols should shard their state across more accounts to increase the cost of write-lock contention. This helps marginally but:

  • Many protocols have irreducible shared state (a single liquidity pool, a global config)
  • Sharding adds complexity and potential consistency bugs
  • Attackers just adjust their spam to cover more accounts

Defensive Patterns That Actually Work

1. Account Write-Lock Minimization

Redesign instructions to minimize write-lock overlap with other instructions:

// BAD: Every swap touches global_state as writable
pub fn swap(ctx: Context<Swap>) -> Result<()> {
    let global = &mut ctx.accounts.global_state;  // Write lock
    global.total_volume += amount;
    // ... swap logic
}

// BETTER: Defer global state updates to a crank
pub fn swap(ctx: Context<SwapOptimized>) -> Result<()> {
    let user_receipt = &mut ctx.accounts.user_receipt;  // Per-user PDA
    user_receipt.pending_volume += amount;
    // ... swap logic (no global write lock)
}

pub fn crank_aggregate(ctx: Context<Crank>) -> Result<()> {
    let global = &mut ctx.accounts.global_state;
    // Batch update from receipts
}
Enter fullscreen mode Exit fullscreen mode

2. Priority Fee Circuit Breakers

Implement off-chain monitoring that detects LDoS patterns and routes users through alternative transaction submission paths:

async function submitWithLDoSProtection(
  tx: Transaction,
  connection: Connection
): Promise<string> {
  const localFee = await getRecentPriorityFee(TARGET_ACCOUNTS);
  const globalMedian = await getGlobalMedianPriorityFee();

  if (localFee > globalMedian * 10) {
    return await submitViaJitoBundle(tx);
  }

  return await connection.sendTransaction(tx);
}
Enter fullscreen mode Exit fullscreen mode

3. Jito Bundle-Based Transaction Submission

Jito bundles let protocols submit transactions that bypass the normal scheduler priority fee auction. By bundling critical transactions (liquidations, oracle updates) through Jito:

  • Transactions land in a specific slot regardless of local fee market conditions
  • The cost is a fixed tip to the block builder, not a priority fee auction
  • Attackers can't outbid bundle inclusion without bribing the block builder directly

4. Validator-Level Account Scheduling Improvements

The Solana validator community is actively working on scheduler improvements:

  • SIMD-0194: Proposes \"fair scheduling\" that gives proportional slot access based on stake weight rather than pure fee competition
  • Account-level rate limiting: Proposals to cap the number of transactions per account per slot
  • Reputation scoring: Track accounts that consistently produce failed transactions and deprioritize them

Detection: Building an LDoS Monitor

import asyncio
from solana.rpc.async_api import AsyncClient
from collections import defaultdict

async def monitor_ldos(
    target_accounts: list[str],
    alert_threshold: float = 10.0,
    window_slots: int = 20
):
    client = AsyncClient("https://api.mainnet-beta.solana.com")
    fee_history = defaultdict(list)

    while True:
        fees = await client.get_recent_prioritization_fees(target_accounts)
        local_median = median([f.prioritization_fee for f in fees])

        global_fees = await client.get_recent_prioritization_fees([])
        global_median = median([f.prioritization_fee for f in global_fees])

        ratio = local_median / max(global_median, 1)
        fee_history['ratio'].append(ratio)

        recent = fee_history['ratio'][-window_slots:]
        if len(recent) >= window_slots and all(r > alert_threshold for r in recent):
            await trigger_alert(
                f"LDoS detected: {window_slots} consecutive slots with "
                f"{ratio:.1f}x fee spike on monitored accounts"
            )

        await asyncio.sleep(0.4)
Enter fullscreen mode Exit fullscreen mode

The Bigger Picture

LDoS attacks represent a fundamental tension in blockchain fee market design: the features that improve normal-case UX often create new attack surfaces.

Solana's localized fee markets are genuinely better for users 99% of the time. But that 1% — when an attacker deliberately targets the isolation boundary — exposes protocols to a new class of economic griefing that didn't exist under global fee markets.

The defense stack is layered:

  1. Protocol design: Minimize shared writable accounts
  2. Transaction infrastructure: Use Jito bundles for critical operations
  3. Monitoring: Detect fee anomalies in real-time
  4. Validator improvements: Long-term scheduler fixes (SIMD-0194 and beyond)

If you're building on Solana in 2026, add \"LDoS resilience\" to your security checklist alongside reentrancy, oracle manipulation, and access control. Your users' ability to interact with your protocol shouldn't depend on whether someone decided to spend $150 to make your accounts expensive today.


This analysis is part of the DeFi Security Research series. Follow for weekly deep dives into smart contract vulnerabilities, exploit post-mortems, and defensive tooling.

Top comments (0)