Abstract: Solana parallelism is enabled by account declarations and limited by them

Solana's throughput does not come from executing every transaction in parallel. It comes from knowing, before execution, which accounts each transaction will read and write. Sealevel can schedule non-conflicting transactions across many execution threads. But if many transactions request a writable lock on the same hot account, those transactions must be serialized. Write-lock exhaustion exploits that resource boundary. An attacker does not need to forge signatures or break consensus. A large volume of valid transactions that all lock the same AMM pool, order book, oracle-adjacent state, vault, or route account can turn local parallel execution into a long queue. For cross-chain routing, this appears as stale quotes, timeout risk, failed swaps, and unstable success rates on specific Solana paths. The defensive goal is not to remove account locks. It is to price hot writable accounts correctly, reject bad queues early, and expose enough telemetry for routers to avoid saturated account sets.

Scope: scheduling and resource defense, not DoS instructions

This article covers Solana and SVM-style account-locking systems from a defensive engineering perspective. It does not provide transaction templates, account target selection methods, or instructions for attacking real protocols. The relevant system roles are user transactions, the runtime, banking stage, account lock tables, leaders, priority fees, local fee markets, RPC forwarding, DEX or oracle program accounts, and cross-chain route engines.

The attacker capability is intentionally limited: submit many valid transactions, choose account lists, pay some fees, and create pressure on local account queues. That is enough to model write-lock exhaustion. This is not a bandwidth-only DoS and not a consensus-failure attack. It is local resource saturation at the execution layer.

Solana transactions declare account addresses and whether each account is writable. Read-only accounts can be shared by many transactions. Writable accounts require exclusive access. This design lets the runtime know conflict relationships before execution, unlike systems where state conflicts are discovered only after executing transactions. The trade-off is visible: when an application concentrates state into a small number of writable accounts, those accounts become the bottleneck.

Conflict graph model: a hot account is a cut vertex

Let `T = {t1, t2, ... tn}` be the transaction set in a scheduling window. Each transaction has a read set `R(t)` and write set `W(t)`. Two transactions conflict if:

`W(a) ∩ (W(b) ∪ R(b)) != ∅` or `W(b) ∩ R(a) != ∅`

Build a conflict graph `G = (V, E)` where each vertex is a transaction and each edge is an account conflict. Alternatively, build a bipartite graph between transactions and accounts, with exclusive capacity on writable account edges. For a hot account `x`, define saturation as:

`sat(x) = demand_write(x) / capacity_write(x)`

In a scheduling window, the writable capacity of a single account is close to a serial channel. If `sat(x) >> 1`, transactions involving `x` queue even when CPU cores and network bandwidth remain available. Global throughput becomes limited by local min-cut behavior. Non-conflicting subgraphs can execute quickly, but paths through the hot vertex serialize.

This is why “more hardware” is not a complete fix. More execution threads increase throughput for independent account sets. They do not let the same writable account be safely modified by several transactions at once. The durable fixes are state sharding, better local fee markets, early queue rejection, and application designs that do not route every user through one global writable state.

Sealevel execution constraints: simulation success is not scheduling success

Sealevel's core advantage is that account access sets are declared up front. The runtime can group non-conflicting transactions into parallel batches, while conflicting transactions are delayed or retried. Transaction forwarding, the transaction pipeline, and priority fees all influence whether a transaction reaches the leader, enters the queue, and gets scheduled. The important resource is not only compute units. It is exclusive lock time on specific accounts.

A simplified scheduler looks like this:

```text for tx in priority_queue: if locks_available(tx.reads, tx.writes): acquire_locks(tx) dispatch(tx) else: defer_or_drop(tx) ```

The hard part is `defer_or_drop`. If the scheduler always keeps conflicting transactions in the queue, bad account sets can build long dependency chains. If it drops too aggressively, legitimate users get worse success rates during congestion. The scheduler has to combine fee, account hotness, historical failure, compute budget, and lock wait time.

Read hotspots and write hotspots should not be treated equally. An oracle account that is mostly read can serve many transactions concurrently. An AMM pool state account, central order book, shared vault, or route intermediate account that must be written becomes an exclusive bottleneck. Application developers should avoid unnecessary shared writes. User-level, order-level, or market-sharded state usually scales better than one global counter or shared route account.

The runtime also has to distinguish lock wait from compute failure. A transaction can simulate successfully and still fail to land because its writable accounts are saturated. It can also land later and see a different account state because earlier transactions modified the same pool or vault. For cross-chain quotes, this distinction is critical. A successful simulation is not proof that the route will schedule in time. A scheduled transaction is not proof that the quoted state remains unchanged.

Leader-side selection is a resource-constrained scheduling problem. A low-fee transaction that occupies a hot write lock may block a series of higher-value transactions behind it. A high-fee transaction with disjoint accounts may be easier to execute alongside other work. The objective is not simply to sort all transactions by fee. It is to maximize executable, useful transactions per unit time under compute and account-lock constraints.

Local fee markets: price the hot writable account, not the whole chain

A global fee market raises costs for everyone. A local fee market tries to charge for the resource that is actually congested. For write-lock exhaustion, the resource is not abstract blockspace. It is exclusive write capacity on specific accounts. A defensive fee model can be written as:

`fee(tx) = base_fee + priority_fee + Σ hotness(a) * write_weight(a)`

where `a ∈ W(tx)`. The term `hotness(a)` can be estimated from recent lock wait, retry rate, write request density, transaction expiry, and business criticality. A nonlinear model can raise costs when saturation crosses a threshold:

`hotness(a) = α * max(0, sat(a) - 1)^2`

The point is not to punish normal users. The point is to make sustained occupation of a hot write lock expensive. If a transaction locks several hot accounts, it should pay for several local resources. A transaction touching cold accounts or read-only accounts should not pay the same price just because one popular pool is congested.

Local fee markets have trade-offs. If the price signal is too strong, arbitrage and liquidation bots can crowd out ordinary users. If it is too weak, malicious or low-quality transactions can cheaply queue behind hot accounts. A better design combines fees with quality signals: recent failure rate, account-set repetition, compute-budget realism, lock-chain length, and whether similar transactions have actually settled.

Fee models must also avoid multi-account dilution. A transaction that locks one extremely hot account and several cold accounts should not look harmless after averaging. A group of transactions rotating across related hot accounts should not evade pricing because each single account appears moderately saturated. Useful metrics include max account hotness, weighted account hotness, and account co-occurrence matrices. Infrastructure accounts such as DEX pools, oracles, shared vaults, and route intermediates may deserve higher business-criticality weights because their congestion affects many downstream users.

Observability is part of the fee market. If wallets, routers, and market makers cannot see account-level wait trends, the market becomes a black box. Even aggregated telemetry helps: write request density, average lock wait, transaction expiry rate, early-drop rate, priority-fee percentiles, and execution success rate. This data lets routing systems move away from saturated account sets before users experience repeated timeouts.

A practical router does not need validator-internal traces, but it does need resource-level signals. For each account-set template, the router can maintain `recentLandingRate`, `medianPriorityFee`, `expiryRate`, `retryCount`, and `stateChangeDrift`. `stateChangeDrift` measures how often the simulated account state differs from the state observed when the transaction actually executes. When drift is high, a quote can be technically valid at simulation time and still be commercially stale by execution time. This is the exact failure mode that matters for swaps.

The fee market should also separate user urgency from spam pressure. A user paying a high priority fee for one time-sensitive swap is different from a bot submitting thousands of near-identical transactions that never settle. Resource pricing can treat both as willingness to pay, but queue defense should also look at repetition and settlement quality. Otherwise attackers can convert the local fee market into a cost-of-doing-business model and continue occupying the same hot write lock.

Static truncation and early drop: reject bad queues before execution

Write-lock defense should happen early. If a transaction reaches execution threads before the system notices a saturated write account, resources have already been spent on forwarding, signature checks, queueing, and scheduling. Static truncation and early drop can reject or downgrade transactions when a hot account's pending write queue is too long and the incoming transaction does not justify more queue pressure.

A defensive state machine can be written as:

```text on incoming_tx(tx): hot = max_hotness(tx.writes) chain = dependency_chain_length(tx.writes) quality = priority_fee_score(tx) + success_prior(tx) if hot > H and chain > L and quality < Q: early_drop(tx) else: enqueue(tx) ```

`success_prior` should not become a centralized whitelist. It should be an explainable statistical signal: whether similar account sets repeatedly fail, whether compute budget is realistic, whether the transaction keeps locking the same hot accounts without settlement, and whether near-identical transactions are copied across many RPC paths. The goal is resource protection, not identity-based censorship.

Early rejection should return structured reasons. `hotWriteAccount`, `insufficientLocalFee`, `dependencyChainTooLong`, `staleBlockhashRisk`, and `duplicateAccountSet` are more useful than a generic failure. Routers can consume those reasons and choose another pool, increase priority fee, refresh a blockhash, or wait for a better window.

Transaction splitting is another possible defense, but it has costs. If a route locks several hot accounts in one transaction, any one conflict can delay the whole route. Splitting may reduce the account set per transaction, but it introduces atomicity and intermediate-state risk. For cross-chain swaps, arbitrary splitting is dangerous because users expect either execution or a clear refund. Whether to split should depend on value, slippage, refund path, and atomicity requirements.

Another defense is state redesign at the application layer. AMMs and routers can reduce write contention by avoiding shared global counters, separating per-market state, making read-only oracle access explicit, and pushing user-specific state into independent accounts where possible. This is not free. More accounts mean more account metadata, more complex routing, and sometimes worse liquidity aggregation. The important point is that application account layout is part of performance security. A protocol that puts every critical path behind one writable account has created a natural DoS choke point.

The scheduler can also use admission bands. Low-hotness account sets enter normally. Medium-hotness sets require adequate local priority fees. High-hotness sets require stronger quality signals, such as realistic compute budget and a lower recent duplicate rate. Extreme-hotness sets can be rejected with a structured reason until the queue cools down. This avoids one binary policy and makes the defense easier to reason about.

The side effect is that admission control becomes part of market design. If thresholds are opaque, users interpret drops as random failure or censorship. If thresholds are public but too simple, bots optimize exactly against them. A pragmatic compromise is to publish coarse reason codes and aggregate queue metrics, while keeping fine-grained anti-spam parameters adaptive. The important invariant is that the rule protects scarce writable-account capacity rather than privileging a particular application.

Failure modes: valid transactions can still create local DoS

The first failure mode is AMM pool write-lock congestion. Many transactions write the same pool state account, so normal swaps and low-quality queue fillers compete for the same lock. Detection signals include rising lock wait, higher failure rates for a specific account, and wider priority-fee distribution.

The second failure mode is bad shared-state design. A state object that could have been read-only becomes a frequently written global account. A route writes a shared vault, global counter, or central accounting state for every user. The mitigation is state sharding and minimizing shared writes.

The third failure mode is long dependency chains. Bots or abnormal workloads submit many transactions with similar account sets, forcing repeated defer and retry behavior. Detection signals include deep queues for the same account combination and repeated expiry. Defenses include early drop, account-set rate limiting, and nonlinear hot-account fees.

The fourth failure mode is local fee spillover. If hot-account premiums are too high, ordinary users cannot access critical pools and routers move to worse liquidity. The answer is not to remove local fees. The answer is to combine fee, liquidity, lock wait, and slippage in route scoring.

The fifth failure mode is RPC-level ambiguity. A user may see “submitted,” while the leader side has not scheduled the transaction or has dropped it because the write locks are saturated. Cross-chain systems must distinguish submitted, scheduled, executed, confirmed, expired, and failed.

AllSwap relevance: account locks are part of route execution quality

AllSwap-style cross-chain routing should compare more than price and nominal chain speed. On Solana paths, account write-lock contention directly affects swap success rate and quote lifetime. A simplified route score is:

`routeScore = priceScore + liquidityScore - lockContentionPenalty - priorityFeeCost - timeoutRisk`

`lockContentionPenalty` comes from recent write-lock wait on the target pool, oracle, vault, and route accounts. `priorityFeeCost` comes from the local fee market. `timeoutRisk` comes from blockhash expiry, retry behavior, and refund timing. A path with a better quote but severe account contention can be worse for the user than a slightly more expensive route with stable execution.

Product state should be more precise than “pending.” `quoteReady` means the route can be priced. `submitted` means the transaction has been sent. `lockContention` means the account set is competing for hot write locks. `rerouting` means the system is switching pools or paths. `executed` means on-chain execution completed. `refundPending` means failure handling is in progress. Users do not need to understand Sealevel, but they do need to know whether delay comes from account locks, chain confirmation, or refund handling.

AllSwap can maintain account-set fingerprints for Solana route templates:

`accountSetHash = H(reads, writes, programs)`

If a fingerprint recently shows high expiry, high retry rate, or high priority-fee pressure, the route engine should lower its weight. If an alternative path uses different pools, vaults, or intermediate accounts, it may have higher actual settlement probability even with a slightly worse price.

Refund handling must also distinguish lock contention from executed-but-unconfirmed state. A transaction stuck behind write locks is different from a transaction already executed and waiting for cross-chain confirmation. The first needs blockhash refresh, retry, and reroute logic. The second needs confirmation and refund proof logic. A single “pending” bucket hides the operational cause.

Route operations can use three thresholds. `softCongestion` means the route remains usable but needs a wider quote window or higher priority fee. `hardCongestion` means the account-set fingerprint should be temporarily downgraded or replaced. `unsafeCongestion` means high-value swaps should not use that path until the lock queue clears. These thresholds should be driven by observed landing rate and expiry rate, not by vague chain-wide sentiment. A fast chain can still have one unusable account set.

This also affects how AllSwap should explain delays. If the Solana transaction has not landed because the write account is saturated, the right message is not the same as “waiting for confirmations.” The user may be better served by rerouting, repricing, or waiting for a new blockhash. If the transaction has landed and the cross-chain leg is waiting, the next action is different. Accurate state naming reduces unnecessary retries and avoids turning user impatience into more lock pressure.

Operationally, AllSwap can track `landingLatency`, `blockhashRefreshCount`, `priorityFeeDelta`, `routeAccountHotness`, and `postSimulationDrift` per Solana path. These metrics should feed route selection before the user signs or submits anything. Once the transaction is already competing for a hot write lock, every blind retry can worsen contention for the same account set.

Open problems: high-performance chains still need resource-pricing language

First, local fee granularity is still evolving. Accounts, programs, write locks, compute units, data bandwidth, and leader queues are all resources. It is not obvious which resource should dominate pricing in every workload.

Second, state sharding creates application complexity. Coarse state causes contention. Fine-grained state improves parallelism but increases routing, accounting, and liquidity fragmentation.

Third, early drop can be mistaken for censorship. Defensive algorithms should be explainable and resource-based, not identity-based.

Fourth, cross-chain routers lack a standard account-lock telemetry interface. RPCs often expose transaction results, but not whether failure came from local fee pressure, compute limits, account locks, or state changes.

Fifth, user experience needs translation. Users should not see graph-theory terms, but they should understand when increasing priority fee, changing route, or waiting for a less congested account set improves success probability.

References

[1] Solana, Sealevel: parallel processing thousands of smart contracts, https://solana.com/news/sealevel---parallel-processing-thousands-of-smart-contracts

[2] Solana documentation, Transactions, https://solana.com/docs/core/transactions

[3] Solana documentation, Accounts, https://solana.com/docs/core/accounts

[4] Solana documentation, Transaction pipeline, https://solana.com/docs/core/transactions/transaction-pipeline

[5] Solana documentation, Fees, https://solana.com/docs/core/fees

[6] Solana documentation, Fee structure, https://solana.com/docs/core/fees/fee-structure

[7] Solana guide, How to use priority fees, https://solana.com/developers/guides/advanced/how-to-use-priority-fees

[8] Solana, Gulf Stream transaction forwarding protocol, https://solana.com/news/gulf-stream--solana-s-mempool-less-transaction-forwarding-protocol

[9] Anza Agave runtime source, Bank implementation, https://github.com/anza-xyz/agave/blob/master/runtime/src/bank.rs

[10] Solana Improvement Documents repository, https://github.com/solana-foundation/solana-improvement-documents

[11] Helius, Solana local fee markets, https://www.helius.dev/blog/solana-local-fee-markets

[12] Helius, Priority fees and transaction fee mechanics, https://www.helius.dev/blog/priority-fees-understanding-solanas-transaction-fee-mechanics

FAQ

What is Solana write-lock exhaustion?

It is local execution congestion caused by many transactions requesting writable access to the same hot account. The transactions can be valid, but Sealevel must serialize writes to that account, creating a queue.

How do local fee markets help with account contention?

A local fee market prices the congested resource directly, such as a hot writable account. Transactions locking saturated accounts pay more, while cold-account or read-only paths avoid paying for unrelated congestion.

Can write-lock exhaustion affect cross-chain swaps?

Yes. If a Solana route depends on a congested AMM, oracle, vault, or route account, the transaction may queue, expire, or fail. Routers should include lock wait, priority fees, and refund timing in quotes.

Does defending against write-lock exhaustion mean rejecting low-fee users?

It should not. A good defense is based on resource use, account hotness, repeated failure, and queue depth rather than identity. Low-fee users can be routed to less congested paths or given clearer wait feedback.

Sources & references