1. 摘要与背景
区块链的计费模式是确保网络安全和有效运行的关键机制,通过收取用户执行操作所需的费用(如Gas费用),防止恶意行为和资源滥用,保护用户利益,并推动整个区块链生态系统的发展和创新。一个有效的计费系统不仅是财务基础,也是促进技术进步和社区信任的重要因素。
本文将深入分析以太坊(ETH)和Solana区块链网络的交易费用模型,以及不安全的交易计费可能引发的网络安全风险。重点讨论CertiK团队发现并修复的Solana网络中大整数模幂运算CU(计算单元)计算错误所引发的潜在远程DOS攻击漏洞,并通过此案例探讨区块链计费模型中的安全隐患。
2. 交易费用模型的重要性
在Web3.0领域中,底层基础设施运行在去中心化的区块链网络上。这些网络由全球验证者共同维护和运营。用户通过交易和智能合约进行互动,所有的交易都被记录在分布式账本上,并且这些记录是永久不可篡改的。
验证者们凭借有限的资源共同维护着这庞大的区块链网络,而交易费用在确保网络稳定性和安全性方面起到关键作用。这些费用不仅激励着网络参与者,还是推动区块链成功发展的动力。有效的交易费用模型能够确保网络资源的合理分配,防止恶意行为和资源滥用,同时保护用户利益,促进技术的持续创新和社区的健康发展。
交易费用模型的安全性对于区块链的长期健康和稳定至关重要。这种模型不仅仅是对网络资源的有效管理,还直接影响用户的信任和参与度。一个健全的交易费用模型能够有效地防止网络遭受恶意行为,如拒绝服务攻击(DDoS),通过设定适当的费用门槛使攻击者难以滥用网络资源。
合理的费用结构能够激励验证者和矿工投入足够的资源来维护区块链的安全性和稳定性,因为他们通过收取交易费用来获取奖励和补偿。此外,透明和公平的计费机制还可以保护用户免受不当收费和资源耗费,增强他们对区块链生态系统的信任感。因此,一个经过良好设计的交易费用模型不仅是经济上的基础,更是确保区块链网络安全和用户权益的重要保障。
3. ETH与Solana网络的交易费用模式设计
在BTC网络中,所有交易的复杂度相对一致,并采用单一的交易计费模型。相比之下,ETH和Solana网络采用图灵完备的脚本语言,其交易计费模型设计更为复杂,涵盖带宽消耗、存储消耗和计算消耗等多个方面。智能合约可以消耗任意数量的带宽、存储和计算资源,而Gas费用则是衡量合约执行所需计算工作量的单位。通过限制Gas费用的消耗,可以有效控制智能合约对资源的过度利用。
3.1:ETH网络的交易计费模型设计
在以太坊(ETH)网络中,交易计费模型设计如下:
3.1.1:单位和概念解释
•Wei和Gwei单位:Wei是以太币(ether)的最小单位,1 ether = 1 x 10^18 wei。Gwei是Gas的计量单位,1 gwei = 1 x 10^9 wei。
3.1.2:Gas费用计算系统
•Gas Limit:用户愿意为确认交易或执行操作支付的最大Gas量。在伦敦升级[1]后,Gas Limit可以根据网络需求在15M至30M之间动态调整。
•Gas Used:实际消耗的Gas数量,不超过Gas Limit。对于未使用的Gas部分,将会自动退回到用户的钱包余额。
•Gas Price:用户愿意为每单位Gas支付的价格。Gas Price随着以太坊网络上交易拥堵情况而变化,通常是动态调整的,当前查询[2]Gas Price约为10gwei左右。伦敦升级引入了改进的EIP-1559[3] 新增两个参数(基础费用BaseFee和优先级费用PriorityFee),改进后的Gas Price计算为BaseFee + PriorityFee。
3.1.3:ETH网络交易费用计算示例
3.2:Solana网络的交易计费模型设计
4. ETH和Solana网络交易费用对比
5. ETH和Solana网络交易计费设计缺陷带来的风险
5.1:ETH网络历史上的计费系统风险
5.2:Solana网络面临过的计费系统风险
6. Solana智能合约的计价模型
7. syscall功能预估模型的CU计算错误
7.1:syscall功能big_mod_exp模块的作用与其CU计算模型
[1u8; len] for the base and exponent
Use a prime or an RSA modulus for each bit-sizes.
计算时间(ns) = bits^2
CU = 计算时间 / 33 ns
CU = 4,194,304 / 33 ≈ 127,100
CU = 52,000,000 / 33 ≈ 1,575,757
7.2:syscall功能big_mod_exp代码深入分析
SyscallBigModExp
中,三个主要输入参数为base、exponent和modulus,分别对应的长度为params.base_len
、params.exponent_len
和params.modulus_len
。需要注意的是,这些参数的单位是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);
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),
),
)?;
CU = bytes^2 / big_modular_exponentiation_cost (33)
CU = bits^2 / 33
CU = (bytes * 8) ^2 / big_modular_exponentiation_cost
CU = 4096 ^ 2 / 33 ~= 508,400
200_000 / 8043 ≈ 24
24 * 37 ms ≈ 890 ms
8. 深入分析Solana区块链与智能合约交互
8.1:POH共识
8.2:并行事务处理
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:深入事务处理代码
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()
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);
}
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()],
)
};
8.4:存在CU漏洞的合约会跨Slot导致事务处理混乱
if bank_slot != working_bank.bank.slot() {
return Err(PohRecorderError::MaxHeightReached);
}
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);
}
.......
}
9. 在搭建的Solana私有集群上复现远程DOS攻击
10. 远程DOS攻击成本
11. 漏洞确认与修复
参考:
[1]
伦敦升级: https://ethereum.org/zh/history/#london[2]
当前查询: https://cn.etherscan.com/Gastracker[3]
EIP-1559: https://eips.ethereum.org/EIPS/eip-1559[4]
当前查询: https://etherscan.io/[5]
查询: https://beta-analysis.solscan.io/public/dashboard/06d689e1-dcd7-4175-a16a-efc074ad5ce2[6]
查询: https://solscan.io/analytics[7]
橘子哨(Tangerine Whistle)分叉: https://ethereum.org/zh/history/#tangerine-whistle[8]
伪龙(Spurious Dragon)分叉: 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]
8项关键创新: https://medium.com/Solana-labs/proof-of-history-a-clock-for-blockchain-cf47a61a9274[12]
2022年1月 – 严重的网络拥塞: https://twitter.com/SolanaStatus/status/1484947431796219906?s=20&t=x6Itu5Yn_8-HtapAyLBrfA[13]
2022年4月和5月 – 短暂的中断: 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]
交易生命周期: 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里面: 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]
生命周期: https://solana.com/docs/advanced/confirmation[24]
chek_transaction_age: https://github.com/anza-xyz/agave/blob/5263c9d61f3af060ac995956120bef11c1bbf182/runtime/src/bank.rs#L3513[25]
修复方案: https://github.com/anza-xyz/agave/commit/eb37b21d4d5ed29d1bf40c9ca7c64509681a2a09