Overview
This article takes an in-depth look at the importance of blockchain transaction fee models and their critical role in ensuring network security and efficient operation. By comparing the transaction fee models of Ethereum and Solana, it highlights how unsafe transaction pricing can introduce network security risks. The article especially focuses on a compute-unit (CU) accounting error in Solana’s big-integer modular exponentiation syscall discovered and reported by the CertiK team, which could lead to a potential remote DoS attack. It further analyzes Solana’s smart-contract pricing model, PoH-related timing mechanics, and parallel transaction processing, and reproduces the remote DoS process and cost via experiments on a private Solana cluster.
1. Summary and Background
A blockchain’s pricing (billing) model is a key mechanism for ensuring network security and effective operation. By charging users for the resources needed to execute operations (e.g., gas fees), the network can prevent malicious behavior and resource abuse, protect user interests, and promote the development and innovation of the overall blockchain ecosystem. An effective pricing system is not only the financial foundation of the network, but also an important factor in advancing technology and building community trust.
This article provides an in-depth analysis of the transaction fee models of Ethereum (ETH) and Solana, as well as the security risks that can arise from unsafe transaction pricing. It focuses on a potential remote DoS vulnerability caused by an error in the compute-unit (CU) calculation of Solana’s big-integer modular exponentiation, discovered and remediated with the help of the CertiK team, and uses this case to discuss security pitfalls in blockchain fee models.
2. Why Transaction Fee Models Matter
In the Web3.0 domain, core infrastructure runs on decentralized blockchain networks maintained and operated by validators worldwide. Users interact through transactions and smart contracts. All transactions are recorded on a distributed ledger and are permanently immutable.
Validators maintain this massive blockchain network with limited resources, and transaction fees play a key role in ensuring network stability and security. Fees not only incentivize network participants, but also serve as a driving force behind blockchain success. A well-designed fee model ensures rational allocation of network resources, prevents malicious behavior and resource abuse, protects users, and supports continuous innovation and healthy community growth.
The security of a transaction fee model is essential for the long-term health and stability of a blockchain. Such a model is not merely a resource management tool—it also directly impacts user trust and participation. A robust fee model can prevent attacks such as DDoS by setting appropriate fee thresholds that make it difficult for attackers to abuse network resources.
A reasonable fee structure motivates validators and miners to commit sufficient resources to maintain security and stability, because they receive compensation via transaction fees. Transparent and fair pricing also protects users from improper charges and unnecessary resource waste, strengthening trust in the ecosystem. Therefore, a well-designed fee model is not only an economic foundation, but also a crucial safeguard for network security and user rights.
3. Transaction Fee Model Design: ETH vs Solana
In the BTC network, transaction complexity is relatively uniform and a single pricing model is used. In contrast, ETH and Solana use Turing-complete scripting/programming models, and their pricing designs are more complex—covering bandwidth, storage, and compute consumption. Smart contracts may consume arbitrary amounts of bandwidth, storage, and compute. Gas fees serve as the unit to measure the computational work needed for execution. By limiting gas consumption, networks can effectively control excessive resource usage.
3.1 Ethereum (ETH) Fee Model Design
3.1.1 Units and Concepts
- Wei and Gwei: Wei is the smallest unit of ether:
1 ether = 1 × 10^18 wei. - Gwei is commonly used for gas pricing:
1 gwei = 1 × 10^9 wei.
3.1.2 Gas Fee System
- Gas Limit: The maximum amount of gas a user is willing to pay to confirm a transaction or execute an operation. After the London upgrade[1], the gas limit can dynamically adjust between 15M and 30M based on network demand.
- Gas Used: The actual amount of gas consumed, not exceeding the gas limit. Unused gas is automatically refunded to the user.
- Gas Price: The price the user is willing to pay per unit of gas. Gas price varies with network congestion and is typically dynamically adjusted. A current query[2] shows gas price is around ~10 gwei. The London upgrade introduced EIP-1559 [3], adding two parameters—BaseFee and PriorityFee—and the updated gas price is computed as:
Gas Price = BaseFee + PriorityFee.
3.1.3 Example: ETH Transaction Fee Calculation
For example, if the gas price is 10 gwei/gas, the transaction fee is calculated as:
Transaction Fee = Gas Price (10 gwei) × Gas Limit (21000) / 10^9 = 0.00021 ETH- At an ETH price of $3500, the fee is approximately $0.735.
- During extreme congestion, gas price may rise above 100 gwei/gas, causing a single transaction to cost more than $10.
3.1.4 ETH TPS
- Transactions per second (TPS) refers to the number of transactions processed per second. A current query [4] shows Ethereum TPS is about 12.7.
These features and parameters enable Ethereum to dynamically adjust fees and effectively manage resources, supporting its broad smart contract and dApp ecosystem.
3.2 Solana Fee Model Design
3.2.1 Gas Fee Calculation
- Native token SOL and lamports: Lamports are the smallest unit of SOL.
1 SOL = 1,000,000,000 lamports. MicroLamports are used for priority fee calculation and equal0.000001 lamports. - Signature fee: Each transaction must include a signature. The base fee per signature is fixed at 5000 lamports (
0.000005 SOL). - Compute Units (CU): The smallest unit used to measure compute resource consumption for smart contract execution. In Solana 1.9.2, a feature similar to ETH’s gas limit was introduced. Each transaction has a default CU budget of 200,000, and can set up to 1,400,000 CU.
3.2.2 Solana Economic Design
- Base fee: For the first two years after Solana launched in 2020, the base transaction fee was fixed at
0.000005 SOLper transaction (one signature per transaction). - Priority fee: Since 2022, Solana introduced an additional priority fee mechanism that allows users to pay extra to have transactions prioritized. The method is to multiply the requested max CU by the CU price of
0.000001 lamports, and round to the nearest lamport. - Rent: The cost for storing data in each Solana account is referred to as “rent.” Validators charge rent based on time and space for data maintained in memory. Accounts require sufficient lamports to become rent-exempt and remain on-chain. Accounts that cannot pay rent may be removed via garbage collection.
- Example: If an account holds at least 2 years’ worth of rent, it is considered rent-exempt. Each time an account’s balance decreases, rent-exemption is checked; transactions that reduce balance below the minimum will fail. The current rent-exempt requirement is 6.96 SOL per MB. When creating a new account, the rent-exempt amount is allocated to that account; when deleting an account, the rent-exempt amount can be reclaimed. For example, an executable program of size 15,000 bytes requires 105,290,880 lamports (
~0.105 SOL) to become rent-exempt.
- Example: If an account holds at least 2 years’ worth of rent, it is considered rent-exempt. Each time an account’s balance decreases, rent-exemption is checked; transactions that reduce balance below the minimum will fail. The current rent-exempt requirement is 6.96 SOL per MB. When creating a new account, the rent-exempt amount is allocated to that account; when deleting an account, the rent-exempt amount can be reclaimed. For example, an executable program of size 15,000 bytes requires 105,290,880 lamports (
3.2.3 Example: Solana Transaction Fee Calculation
- Total fee: Suppose a transaction requests 1,000,000 CU and sets a CU price of 10,000 MicroLamports. Then:
Total Fee = 5000 lamports (signature fee) + 1,000,000 CU × 10,000 MicroLamports × 0.000001 lamports = 0.000015 SOL- A current Solana average fee query [5] shows an average fee of about
0.000021 SOL, and an extra fee of about0.000025 SOL. At1 SOL = $100, the fee is about $0.0021 per transaction.
- CU time limit: CU can also be priced by execution time in the Solana SVM, where 33 ns of compute time equals 1 CU. With the default cap of 200,000 CU, execution time is about 6.6 ms; with the max 1,400,000 CU, execution time is about 46.2 ms.
- Fee distribution: Solana burns 50% of transaction fees and distributes the remaining 50% to the validator that processes the transaction.
3.2.4 Solana TPS
- According to the Solana whitepaper, Solana can theoretically process up to 710,000 TPS. In practice, Solana has demonstrated over 5,000 TPS, and reached 65,000 TPS in testing. A current query [6] shows Solana TPS is about 3300.
These designs and parameters allow Solana to handle a large number of transactions efficiently and offer flexible fee/resource management, supporting its fast growth and smart contract/dApp ecosystem.
4. ETH vs Solana Transaction Fee Comparison
4.1 Gas Fee Comparison
Based on average fee queries, Solana’s per-transaction fee is about $0.0021, far lower than Ethereum’s $0.7. Ethereum fees rise with congestion, while Solana remains very cheap—about 500 transactions total cost roughly $1.
4.2 TPS Comparison
Currently, Solana’s TPS is about 250× to 500× Ethereum’s TPS (i.e., Ethereum TPS is about 1/12 of Solana’s 3000–5000 TPS).
4.3 Summary
Overall, Solana can process a large number of transactions at very low gas cost. However, Solana faces a higher risk of spam transactions than Ethereum, which poses significant challenges to network stability and resiliency.
5. Risks Caused by Fee-Model Design Flaws
After understanding and comparing Ethereum and Solana fee models, we can see that flaws in fee design can lead to serious network security risks. DoS attacks are one of the major security issues affecting Turing-complete blockchains. The following sections discuss fee-model-related DoS risks in Ethereum and Solana.
5.1 Historical Fee-Model Risks in Ethereum
Ethereum experienced two DoS attacks that degraded or crashed network performance. The solutions were the Tangerine Whistle hard fork on Oct 18, 2016 [7] and the Spurious Dragon hard fork on Nov 22, 2016 [8].
5.1.1 Root Cause: EXTCODESIZE Opcode Attack
The culprit was the EXTCODESIZE opcode. Because its gas cost was low and required nodes to read state from disk, attackers could call it around 50,000 times per block as long as total gas stayed within the block gas limit, greatly extending compute time and effectively paralyzing Ethereum.
- Fix (EIP-150) [9]: Increase EXTCODESIZE gas cost from 20 to 700 to prevent recurrence.
5.1.2 Root Cause: SELFDESTRUCT Opcode Attack
Attackers used SELFDESTRUCT to generate many empty accounts. Each SELFDESTRUCT cost 90 gas to create an empty account stored in Ethereum’s state trie. Attackers created about 19 million empty accounts, greatly inflating state storage and causing severe node storage pressure.
- Fix (EIP-161) [10]: Clear empty accounts and optimize state storage to reduce waste. Now creating a new account requires an extra 25,000 gas; empty accounts are functionally equivalent to non-existent accounts and can be deleted by interacting with them via transactions.
5.1.3 Summary
Both DoS attacks exploited underpriced opcodes, amplifying attack effectiveness. Attackers could impose heavy network load with low cost, exposing the fragility of low-gas mechanisms.
5.2 Fee-Model Risks Faced by Solana
Ethereum faced major DoS attacks in 2016. At that time, Solana was still in its early founding stage and was seen as a potential “Ethereum killer.” Solana experienced severe network outages in 2022 (five outages total), but only one outage in 2023. As a high-performance blockchain, Solana introduced eight key innovations [11], which advanced technology but also introduced unknown risks.
In multiple outage incidents, Solana suffered DoS due to spam transactions in January 2022 and April 2022.
5.2.1 January 2022 – Severe Network Congestion [12]
Excessive duplicate transactions exhausted caches, severely degrading performance, limiting node throughput for a prolonged time.
5.2.2 April and May 2022 – Short Outages [13]
In April 2022, Solana went down for 2 hours 42 minutes due to NFT bots spamming the network. In May 2022, Solana went down again for 5 hours 31 minutes due to spam attacks. At 6 million spam transactions per second, validator memory was exhausted and network traffic exceeded 100 Gbps, causing consensus stalls on mainnet-beta. Insufficient voting to clear old blocks further worsened memory usage, leading to validator crashes.
Similar to Ethereum’s 2016 DoS attacks, Solana mitigated attacks by increasing gas costs after several outages. In particular, priority fees were introduced to increase attack cost and reduce spam viability.
5.2.3 Summary
For both Ethereum and Solana, correct fee-model design is crucial for mitigating DoS attacks. Attackers often exploit vulnerabilities in fee mechanisms. While priority fees increase attack cost, Solana’s overall transaction cost remains very low, suggesting potential unknown attack surfaces in the future.
6. Solana Smart Contract Pricing Model
6.1 Precise CU Accounting Model for Solana Contracts
The Solana Virtual Machine (SVM) is crucial for executing contracts on Solana due to its efficient CU model and fast execution. With precise pricing and an optimized execution engine, SVM supports high throughput and low latency while ensuring contract security and stability. Solana’s contract billing uses a precise CU model. Each 33 ns of compute time corresponds to 1 CU, and CU is the standard unit of resource consumption in the SVM. Execution cost is based on the CU consumed by bytecode execution.

The SVM CU accounting model is critical to Solana. Under certain conditions the CU accounting instruction may be incorrectly translated, causing a contract to consume infinite compute in the SVM and potentially crash the Solana network.
6.2 Syscalls Use an Estimated (Approximate) Accounting Model
As Solana evolves, it may introduce new functionality or patches that change cluster behavior and program execution. These changes can be implemented by adding or removing syscall functionality.
Solana supports a “runtime feature” mechanism similar to hot patches, adjusting program behavior when cluster behavior changes. These changes are gated by syscall features and are disabled by default unless enabled.
Syscall billing is implemented by assigning fixed CU values to specific syscalls, defined in the compute budget.
In CU-accounting research, syscall execution does not fully rely on the standard SVM model; instead, it uses an estimated accounting model and may depend on third-party libraries. This makes syscalls flexible to introduce but also increases complexity.

7. CU Accounting Error in the Syscall Estimated Model
Solana’s gas cost limit is enforced via CU limits. CU accounting errors can cause on-chain execution to exceed CPU/time constraints, leading to potential remote DoS. In studying Solana syscalls and comparing historical vulnerabilities in Ethereum and Solana, the CertiK team conducted deep analysis of syscall gas costs and found that the big_mod_exp syscall has a vulnerability in CU cost calculation due to mixing bits and bytes, causing a severe mismatch between resource usage and accounting, which can lead to remote DoS.
7.1 Purpose of big_mod_exp and Its CU Model
7.1.1 The Pull Request Introducing It
Solana introduced big-integer modular exponentiation via syscall. See: add big_mod_exp syscall #28503 [14]. This is similar to EIP-198 [15]. The CU model is:
[1u8; len] for the base and exponent
Use a prime or an RSA modulus for each bit-sizes.
7.1.2 Expected CU vs Time (Test Results)
Because this syscall targets RSA-like use cases, input supports up to 2048-bit (supported sizes: 32, 64, 128, 256, 512, 1024, 2048). The formula:
Execution Time (ns) = bits^2CU = Execution Time / 33 ns
For 2048-bit input:
- Execution time:
4,194,304 ns CU = 4,194,304 / 33 ≈ 127,100
Note: Solana also introduced up to 4096-bit. For 4096-bit input:
- Execution time:
52 ms CU = 52,000,000 / 33 ≈ 1,575,757
This suggests CU grows with bit-length squared; larger bit sizes consume more CU.
7.2 Deep Dive: big_mod_exp Code
When Solana introduced #28503[16], the expected CU budget for 4096-bit was 1,575,757, but tests found the budget was only 8043—indicating a CU calculation issue.
The critical code is in SyscallBigModExp. The three main inputs are base, exponent, and modulus, with lengths params.base_len, params.exponent_len, params.modulus_len. These lengths are in bytes:
let params = &translate_slice::<BigModExpParams>(
memory_mapping,
params,
1,
invoke_context.get_check_aligned(),
invoke_context.get_check_size(),
)?
.get(0)
.ok_or(SyscallError::InvalidLength)?;
if params.base_len > 512 || params.exponent_len > 512 ||
params.modulus_len > 512 {
return Err(Box::new(SyscallError::InvalidLength));
}
let input_len: u64 = std::cmp::max(params.base_len, params.exponent_len);
let input_len: u64 = std::cmp::max(input_len, params.modulus_len);
The CU consumption logic:
let budget = invoke_context.get_compute_budget();
consume_compute_meter(
invoke_context,
budget.syscall_base_cost.saturating_add(
input_len
.saturating_mul(input_len)
.checked_div(budget.big_modular_exponentiation_cost)
.unwrap_or(u64::MAX),
),
)?;
From this, CU is:
CU = bytes^2 / big_modular_exponentiation_cost (33)
But the original intended formula was:
CU = bits^2 / 33
This reveals the vulnerability core: the input unit is bytes but should have been converted to bits. The correct CU should be:
CU = (bytes × 8)^2 / big_modular_exponentiation_cost
For 4096-bit input, correct CU:
CU = 4096^2 / 33 ≈ 508,400
With the vulnerability, at default 200,000 CU, a 4096-bit big_mod_exp contract execution time:
200,000 / 8043 ≈ 2424 × 37 ms ≈ 890 ms
At max 1,400,000 CU, a single contract execution time is about 6.23 seconds.
Solana’s average block time is about 400–600 ms (currently ~400 ms per Solana Explorer [17]). Considering PoH and parallel processing, can a single 6.23-second contract cause remote DoS? What if multiple contracts run in parallel? The following sections discuss Solana consensus and transaction processing and reproduce a remote DoS on a private cluster using the vulnerable big_mod_exp contract.
8. Deep Dive: Solana Blockchain and Smart Contract Interaction
8.1 PoH Consensus
Slot and Epoch are Solana time units. Slot is the smallest unit; 432,000 slots make one epoch. PoH (Proof of History) uses a verifiable delay function (VDF) to generate an unpredictable time sequence used to timestamp blocks in each slot.

Each Solana transaction has a lifecycle [18]: it includes a “recent blockhash” used as a PoH clock timestamp, and expires when the blockhash is no longer sufficiently “recent,” within 150 slots.
Validators look up the slot number of the blockhash for each transaction. If they cannot find it, or the slot is more than 150 behind the current slot, the transaction is rejected:

Solana’s workflow involves six basic steps:
- Leader election and slot assignment: Solana rotates leaders via a PoS-based election mechanism. Each leader is assigned four consecutive slots to process data, totaling about 1.6 seconds (4 blocks × 400 ms each). Leader election is random, proportional to stake weight, and the election cycle is about 2–3 days (432,000 slots).
- Leader ordering and maximizing data flow: Leaders use PoH to generate a verifiable time sequence, ensuring read consistency and verifiable time. Leaders order user transactions so validators process them consistently, maximizing throughput and keeping the network efficient.
- Leader executes transactions: Leaders store user transactions in RAM and execute them on the current state, handling token transfers and smart contract execution. Temporary RAM storage improves speed and throughput.
- Leader publishes results: After execution and state updates, leaders sign the hash of the transaction set and final state and publish them to validators (replicas), ensuring integrity and verifiability.
- Validators verify: Validators re-execute transactions on their local state copies to confirm correctness. They use fork-choice rules to evaluate leader blocks and maintain network consistency.
- Consensus confirmation: Validators gossip their state signatures as votes to finalize block validity and reach network consensus.

8.2 Parallel Transaction Processing
Solana validators execute multiple queues using multithreading. One thread processes one queue. NUM_THREADS and MIN_TOTAL_THREADS control thread counts:
pub const NUM_THREADS: u32 = 6;
const NUM_VOTE_PROCESSING_THREADS: u32 = 2;
const MIN_THREADS_BANKING: u32 = 1;
const MIN_TOTAL_THREADS: u32 = NUM_VOTE_PROCESSING_THREADS +
MIN_THREADS_BANKING;
...
pub fn num_threads() -> u32 {
cmp::max(
env::var("Solana_BANKING_THREADS")
.map(|x| x.parse().unwrap_or(NUM_THREADS))
.unwrap_or(NUM_THREADS),
MIN_TOTAL_THREADS,
)
}
8.3 Deep Dive into Transaction Processing Code
Solana transaction processing runs in multiple threads via process_loop [19], continuously processing transactions.
Builder::new()
.name(format!("solBanknStgTx{id:02}"))
.spawn(move || {
Self::process_loop(
&mut packet_receiver,
&decision_maker,
&mut forwarder,
&consumer,
id,
unprocessed_transaction_storage,
)
})
.unwrap()
Inside process_loop [20], it loops over transaction hashes from unprocessed_transaction_storage. process_buffered_packets passes them down to the SVM, while receive_and_buffer_packets receives incoming transaction hashes:
loop {
if !unprocessed_transaction_storage.is_empty()
|| last_metrics_update.elapsed() >= SLOT_BOUNDARY_CHECK_PERIOD
{
let (_, process_buffered_packets_time) = measure!(
Self::process_buffered_packets(
decision_maker,
forwarder,
consumer,
&mut unprocessed_transaction_storage,
&banking_stage_stats,
&mut slot_metrics_tracker,
&mut tracer_packet_stats,
),
"process_buffered_packets",
);
slot_metrics_tracker
.increment_process_buffered_packets_us(process_buffered_packets_time.as_us());
last_metrics_update = Instant::now();
}
tracer_packet_stats.report(1000);
match packet_receiver.receive_and_buffer_packets(
&mut unprocessed_transaction_storage,
&mut banking_stage_stats,
&mut tracer_packet_stats,
&mut slot_metrics_tracker,
) {
Ok(()) | Err(RecvTimeoutError::Timeout) => (),
Err(RecvTimeoutError::Disconnected) => break,
}
banking_stage_stats.report(1000);
}
A key function is execute_and_commit_transactions_locked [21]. It executes instructions in the SVM, checks errors, commits transactions, records them via record_transactions, and commits via committer.commit_transactions:
let (load_and_execute_transactions_output, load_execute_us) = measure_us!(bank
.load_and_execute_transactions(
batch,
MAX_PROCESSING_AGE,
&mut execute_and_commit_timings.execute_timings,
TransactionProcessingConfig {
account_overrides: None,
log_messages_bytes_limit: self.log_messages_bytes_limit,
limit_to_load_programs: true,
recording_config: ExecutionRecordingConfig::new_single_setting(
transaction_status_sender_enabled
),
}
));
execute_and_commit_timings.load_execute_us = load_execute_us;
.......
let (record_transactions_summary, record_us) = measure_us!(self
.transaction_recorder
.record_transactions(bank.slot(), executed_transactions));
execute_and_commit_timings.record_us = record_us;
.......
let (commit_time_us, commit_transaction_statuses) = if executed_transactions_count != 0 {
self.committer.commit_transactions(
batch,
&mut loaded_transactions,
execution_results,
last_blockhash,
lamports_per_signature,
starting_transaction_index,
bank,
&mut pre_balance_info,
&mut execute_and_commit_timings,
signature_count,
executed_transactions_count,
executed_non_vote_transactions_count,
executed_with_successful_result_count,
)
} else {
(
0,
vec![CommitTransactionDetails::NotCommitted; execution_results.len()],
)
};
A normal execution flow is:
SVM execution → record transactions → commit transactions.

8.4 A CU-Buggy Contract Can Cross Slots and Disrupt Processing
After analyzing PoH timing and parallel processing, we introduce the big_mod_exp contract with CU-accounting vulnerability to study its on-chain impact.
Executing a contract once with 4096-bit big_mod_exp, under the default 200,000 CU cap, takes ~890 ms, which can span 1–2 slots. When it crosses slots, record [22] fails because the transaction’s original slot is behind the current slot by 1–2 slots, returning PohRecorderError::MaxHeightReached:
if bank_slot != working_bank.bank.slot() {
return Err(PohRecorderError::MaxHeightReached);
}
As discussed, transactions have a lifecycle [23], controlled by MAX_PROCESSING_AGE = 150. The vulnerable contract crosses slots, causing failure; then Solana checks slot age. If it is within 150 slots, Solana sets retryable_transaction_indexes and retries by pushing packets back into the retry queue:
fn process_packets<F>(
&mut self,
bank: &Bank,
banking_stage_stats: &BankingStageStats,
slot_metrics_tracker: &mut LeaderSlotMetricsTracker,
mut processing_function: F,
) -> bool
where
F: FnMut(
&Vec<Arc<ImmutableDeserializedPacket>>,
&mut ConsumeScannerPayload,
) -> Option<Vec<usize>>,
{
let mut retryable_packets = self.take_priority_queue();
let original_capacity = retryable_packets.capacity();
let mut new_retryable_packets = MinMaxHeap::with_capacity(original_capacity);
let all_packets_to_process = retryable_packets.drain_desc().collect_vec();
..............
..........
while let Some((packets_to_process, payload)) = scanner.iterate() {
let packets_to_process = packets_to_process
.iter()
.map(|p| (*p).clone())
.collect_vec();
let retryable_packets = if let Some(retryable_transaction_indexes) =
processing_function(&packets_to_process, payload)
{
Self::collect_retained_packets(
payload.message_hash_to_transaction,
&packets_to_process,
&retryable_transaction_indexes,
)
} else {
packets_to_process
};
new_retryable_packets.extend(retryable_packets);
}
.......
}
Therefore, the vulnerable big_mod_exp contract will be repeatedly executed up to 150 times before the transaction expires and fails. MAX_PROCESSING_AGE is checked in chek_transaction_age [24]. With 4096-bit big_mod_exp:
200,000 / 8043 ≈ 24, time:24 × 37 ms ≈ 890 ms- After 150 retries, total time:
133,500 ms ≈ 130 s
This long-running retry loop occupies resources and blocks queues, following this process:

9. Reproducing Remote DoS on a Private Solana Cluster
A local test prepared 10–20 independent accounts. Each account invoked a contract using 4096-bit big_mod_exp once under the default 200,000 CU cap, while normal contract calls from multiple accounts ran in parallel for comparison.
Private cluster setup: 4 nodes total: Leader Node, User RPC Node, Attacker RPC Node, Node4. The user interface invoked normal contracts from 10 independent accounts. Average time from RPC request to response completion was ~740 ms.
The attacker interface simulated attacks using 20 independent accounts, where each account invoked the 4096-bit big_mod_exp contract 20 times.
The screenshot below shows the scenario where the attacker sent 20 malicious contract calls. The Leader Node CPU is fully saturated, and the user interface receives no RPC responses for normal calls:

Results show that both the private and local clusters experience long blocking (up to 133 seconds) when processing multiple (20) independent accounts invoking the 4096-bit big_mod_exp contract—20 total calls. Repeated attacks can cause severe DoS:

10. Remote DoS Attack Cost
Estimated cost per attack: When the 4096-bit big_mod_exp contract runs on a local cluster, because the transaction expires and is dropped, gas fees later are 0. Solana’s CU accounting is analogous to ETH gas accounting, limiting resource usage to prevent remote DoS. However, Solana’s estimated accounting model for syscalls introduced a CU-accounting vulnerability, causing severe mismatch between resource usage and billed cost, ultimately enabling potential remote DoS.
Fortunately, before introducing new syscall functionality, Solana performs extensive testing and validation, including collaborating with external security researchers via bug bounty programs, ensuring stability after deployment. This big_mod_exp syscall vulnerability was discovered early, preserving Solana network stability and security.
11. Vulnerability Confirmation and Fix
Solana confirmed that the CU-accounting error in big_mod_exp reported by the CertiK team could lead to potential remote DoS and classified it as a DoS vulnerability. Solana developers’ fix [25] recalculated big_mod_exp CU cost by re-benchmarking modular exponentiation performance and adjusting CU to:
CU = N^2/2 + 190
Although the fix did not simply replace bytes with bits, it recalibrated the CU cost and resolved the vulnerability. According to the security advisory, the algorithm may be further optimized in the future for better performance.

References
- [1] London upgrade: https://ethereum.org/zh/history/#london
- [2] Gas tracker: https://cn.etherscan.com/Gastracker
- [3] EIP-1559: https://eips.ethereum.org/EIPS/eip-1559
- [4] TPS query: https://etherscan.io/
- [5] Solana avg fee dashboard: https://beta-analysis.solscan.io/public/dashboard/06d689e1-dcd7-4175-a16a-efc074ad5ce2
- [6] Solscan analytics: https://solscan.io/analytics
- [7] Tangerine Whistle fork: https://ethereum.org/zh/history/#tangerine-whistle
- [8] Spurious Dragon fork: https://ethereum.org/zh/history/#spurious-dragon
- [9] EIP-150: https://eips.ethereum.org/EIPS/eip-150
- [10] EIP-161: https://eips.ethereum.org/EIPS/eip-161
- [11] Eight key innovations: https://medium.com/Solana-labs/proof-of-history-a-clock-for-blockchain-cf47a61a9274
- [12] Jan 2022 congestion: https://twitter.com/SolanaStatus/status/1484947431796219906?s=20&t=x6Itu5Yn_8-HtapAyLBrfA
- [13] Apr/May 2022 outages: https://solana.com/news/04-30-22-Solana-mainnet-beta-outage-report-mitigation
- [14] add big_mod_exp syscall #28503: https://github.com/Solana-labs/Solana/pull/28503
- [15] EIP-198: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md
- [16] #28503: https://github.com/Solana-labs/Solana/pull/28503
- [17] Solana Explorer: https://explorer.solana.com/
- [18] Transaction lifetime: https://docs.solana.com/developing/transaction_confirmation
- [19] process_loop: https://github.com/anza-xyz/agave/blob/5263c9d61f3af060ac995956120bef11c1bbf182/core/src/banking_stage.rs#L644C8-L656C22
- [20] process_loop details: https://github.com/anza-xyz/agave/blob/5263c9d61f3af060ac995956120bef11c1bbf182/core/src/banking_stage.rs#L742
- [21] execute_and_commit_transactions_locked: https://github.com/anza-xyz/agave/blob/5263c9d61f3af060ac995956120bef11c1bbf182/core/src/banking_stage/consumer.rs#L569
- [22] record: https://github.com/anza-xyz/agave/blob/master/poh/src/poh_recorder.rs#L942
- [23] Confirmation lifecycle: https://solana.com/docs/advanced/confirmation
- [24] chek_transaction_age: https://github.com/anza-xyz/agave/blob/5263c9d61f3af060ac995956120bef11c1bbf182/runtime/src/bank.rs#L3513
- [25] Fix commit: https://github.com/anza-xyz/agave/commit/eb37b21d4d5ed29d1bf40c9ca7c64509681a2a09



