Solana FeeStructure(综合费用) 计算手续费和优先级费用
计算交易打包检查
core/src/banking_stage/transaction_scheduler/scheduler_controller.rs
fn buffer_packets(&mut self, packets: Vec<ImmutableDeserializedPacket>) {
...
for ((transaction, fee_budget_limits), _) in transactions
.into_iter()
.zip(fee_budget_limits_vec)
.zip(check_results)
.filter(|(_, check_result)| check_result.0.is_ok())
{
...
let transaction_id = self.transaction_id_generator.next();
let (priority, cost) =
Self::calculate_priority_and_cost(&transaction, &fee_budget_limits, &bank);// 计算优先级和手续费费用
let transaction_ttl = SanitizedTransactionTTL {
transaction,
max_age_slot: last_slot_in_epoch,
};
if self.container.insert_new_transaction(
transaction_id,
transaction_ttl,
priority,
cost,
) {
计算交易所需的Fee
fn calculate_priority_and_cost(
transaction: &SanitizedTransaction,
fee_budget_limits: &FeeBudgetLimits,
bank: &Bank,
) -> (u64, u64) {
let cost = CostModel::calculate_cost(transaction, &bank.feature_set).sum();
let fee = bank.fee_structure.calculate_fee(
transaction.message(),
5_000, // this just needs to be non-zero
fee_budget_limits,
bank.feature_set
.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()),
bank.feature_set
.is_active(&remove_rounding_in_fee_calculation::id()),
);
// 我们在这里需要一个乘数,以避免过度向下舍入。
// 对于许多交易来说,成本将高于原始 Lamport 的费用。
// 为了计算优先级,我们将费用乘以一个大数,以便成本只是一小部分。
// 分母中使用 1 的偏移量来明确避免除以零。
const MULTIPLIER: u64 = 1_000_000;
(
fee.saturating_mul(MULTIPLIER)
.saturating_div(cost.saturating_add(1)), // 优先级费用
cost, // 交易fee
)
}
从返回值作用,cost等于交易的Fee, 而calculate_fee计算的fee用于计算交易的优先级
计算Cost
cost-model/src/cost_model.rs
pub fn calculate_cost(
transaction: &SanitizedTransaction,
feature_set: &FeatureSet,
) -> TransactionCost {
if transaction.is_simple_vote_transaction() { // 判断是否为简单的验证者投票交易
TransactionCost::SimpleVote {
writable_accounts: Self::get_writable_accounts(transaction),
}
} else { // 常规交易
let mut tx_cost = UsageCostDetails::new_with_default_capacity();
Self::get_signature_cost(&mut tx_cost, transaction);
Self::get_write_lock_cost(&mut tx_cost, transaction, feature_set);
Self::get_transaction_cost(&mut tx_cost, transaction, feature_set);
tx_cost.account_data_size = Self::calculate_account_data_size(transaction);
debug!("transaction {:?} has cost {:?}", transaction, tx_cost);
TransactionCost::Transaction(tx_cost)
}
}
impl Default for UsageCostDetails {
fn default() -> Self {
Self {
writable_accounts: Vec::with_capacity(MAX_WRITABLE_ACCOUNTS),
signature_cost: 0u64,
write_lock_cost: 0u64,
data_bytes_cost: 0u64,
programs_execution_cost: 0u64,
loaded_accounts_data_size_cost: 0u64,
account_data_size: 0u64,
num_transaction_signatures: 0u64,
num_secp256k1_instruction_signatures: 0u64,
num_ed25519_instruction_signatures: 0u64,
}
}
}
综合费用因素计算
- 签名数量
每个签名固定费率 - 写锁数量
每个可写账户的固定利率 - 数据字节成本
所有交易指令数据的长度总和的每字节固定费率 - 账户规模
账户规模无法预先得知,但可以占到交易在网络上产生的相当一部分负载。付款人将预先支付最大账户规模(1000 万)的费用,并在实际账户规模得知后退还差额。 - 计算预算
每笔交易将获得默认的交易范围计算预算 20 万个单位,并可选择通过计算预算指令请求更大的预算,最高可达 100 万个单位。此预算用于限制处理交易所需的时间。费用的计算预算部分将根据默认或请求的金额预先收取。处理后,将知道实际消耗的单位数,并将向付款人退还差额,因此付款人只需支付他们使用的费用。内置程序将具有固定成本,而 SBF 程序的成本将在运行时衡量。 - 预编译程序
预编译程序正在执行计算密集型操作。预编译程序所产生的工作可以根据指令的数据数组进行预测。因此,将根据指令数据的解析为每个预编译程序分配成本。由于预编译程序是在银行之外处理的,因此它们的计算成本不会反映在计算预算中,也不会用于事务调度决策。
计算Fee
sdk/src/fee.rs
#[cfg(not(target_os = "solana"))]
pub fn calculate_fee(
&self,
message: &SanitizedMessage,
lamports_per_signature: u64,
budget_limits: &FeeBudgetLimits,
include_loaded_account_data_size_in_fee: bool,
remove_rounding_in_fee_calculation: bool,
) -> u64 {
// Fee based on compute units and signatures
let congestion_multiplier = if lamports_per_signature == 0 {
0 // test only
} else {
1 // multiplier that has no effect
};
self.calculate_fee_details(
message,
budget_limits,
include_loaded_account_data_size_in_fee,
)
.total_fee(remove_rounding_in_fee_calculation)
.saturating_mul(congestion_multiplier)
}
#[cfg(not(target_os = "solana"))]
pub fn calculate_fee_details(
&self,
message: &SanitizedMessage,
budget_limits: &FeeBudgetLimits,
include_loaded_account_data_size_in_fee: bool,
) -> FeeDetails {
let signature_fee = message
.num_signatures()
.saturating_mul(self.lamports_per_signature);
let write_lock_fee = message
.num_write_locks()
.saturating_mul(self.lamports_per_write_lock);
// `compute_fee` covers costs for both requested_compute_units and
// requested_loaded_account_data_size
let loaded_accounts_data_size_cost = if include_loaded_account_data_size_in_fee {
FeeStructure::calculate_memory_usage_cost(
budget_limits.loaded_accounts_data_size_limit,
budget_limits.heap_cost,
)
} else {
0_u64
};
let total_compute_units =
loaded_accounts_data_size_cost.saturating_add(budget_limits.compute_unit_limit);
let compute_fee = self
.compute_fee_bins
.iter()
.find(|bin| total_compute_units <= bin.limit)
.map(|bin| bin.fee)
.unwrap_or_else(|| {
self.compute_fee_bins
.last()
.map(|bin| bin.fee)
.unwrap_or_default()
});
FeeDetails {
transaction_fee: signature_fee
.saturating_add(write_lock_fee)
.saturating_add(compute_fee),
prioritization_fee: budget_limits.prioritization_fee,
}
}
交易打包余额检查
交易执行检查时,一样会计算交易的fee,确保支付地址余额充足
core/src/banking_stage/unprocessed_transaction_storage.rs
fn consume_scan_should_process_packet(
bank: &Bank,
banking_stage_stats: &BankingStageStats,
packet: &ImmutableDeserializedPacket,
payload: &mut ConsumeScannerPayload,
) -> ProcessingDecision {
...
// 仅当我们确实可以采取锁定时才检查费用支付者
// 我们不会在此立即丢弃检查锁定失败,
// 因为优先级保护要求我们始终采取锁定
// 除非在丢弃交易的情况下(即“从不”)。
if payload.account_locks.check_locks(message)
&& Consumer::check_fee_payer_unlocked(bank, message, &mut payload.error_counters)
.is_err()
{
payload
.message_hash_to_transaction
.remove(packet.message_hash());
return ProcessingDecision::Never;
}
core/src/banking_stage/consumer.rs
pub fn check_fee_payer_unlocked(
bank: &Bank,
message: &SanitizedMessage,
error_counters: &mut TransactionErrorMetrics,
) -> Result<(), TransactionError> {
let fee_payer = message.fee_payer();
let budget_limits =
process_compute_budget_instructions(message.program_instructions_iter())?.into();
let fee = bank.fee_structure.calculate_fee( // 同上,计算交易的fee
message,
bank.get_lamports_per_signature(),
&budget_limits,
bank.feature_set.is_active(
&feature_set::include_loaded_accounts_data_size_in_fee_calculation::id(),
),
bank.feature_set
.is_active(&feature_set::remove_rounding_in_fee_calculation::id()),
);
let (mut fee_payer_account, _slot) = bank
.rc
.accounts
.accounts_db
.load_with_fixed_root(&bank.ancestors, fee_payer)
.ok_or(TransactionError::AccountNotFound)?;
validate_fee_payer(
fee_payer,
&mut fee_payer_account,
0,
error_counters,
bank.rent_collector(),
fee,
)
}