use {
crate::{
account_saver::collect_accounts_to_store,
bank::{
entry_bytes_budget::EntryBytesBudget,
metrics::*,
partitioned_epoch_rewards::{
CachedVoteAccounts, EpochRewardStatus, RewardCommissionAccounts,
},
},
bank_forks::BankForks,
epoch_stakes::{
BLSPubkeyToRankMap, DeserializableVersionedEpochStakes, NodeVoteAccounts,
VersionedEpochStakes,
},
inflation_rewards::points::InflationPointCalculationEvent,
installed_scheduler_pool::{BankWithScheduler, InstalledSchedulerRwLock},
rent_collector::RentCollector,
reward_info::RewardInfo,
runtime_config::RuntimeConfig,
stake_account::StakeAccount,
stake_history::StakeHistory as CowStakeHistory,
stake_weighted_timestamp::{
MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST, MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW_V2,
MaxAllowableDrift, calculate_stake_weighted_timestamp,
},
stakes::{DeserializableStakes, SerdeStakesToStakeFormat, Stakes, StakesCache},
status_cache::{SlotDelta, StatusCache},
transaction_batch::{OwnedOrBorrowed, TransactionBatch},
},
accounts_lt_hash::{CacheValue as AccountsLtHashCacheValue, Stats as AccountsLtHashStats},
agave_bls_cert_verify::cert_verify::{self, Error as CertVerifyError},
agave_feature_set::{
self as feature_set, FeatureSet, increase_cpi_account_info_limit,
raise_cpi_nesting_limit_to_8, relax_programdata_account_check_migration,
},
agave_precompiles::{get_precompile, get_precompiles, is_precompile},
agave_reserved_account_keys::ReservedAccountKeys,
agave_snapshots::snapshot_hash::SnapshotHash,
agave_syscalls::{
create_program_runtime_environment_v1, create_program_runtime_environment_v2,
},
agave_votor_messages::{
consensus_message::Certificate, migration::GENESIS_CERTIFICATE_ACCOUNT,
},
ahash::AHashSet,
dashmap::DashMap,
log::*,
partitioned_epoch_rewards::PartitionedRewardsCalculation,
rayon::{ThreadPool, ThreadPoolBuilder},
serde::{Deserialize, Serialize},
solana_account::{
Account, AccountSharedData, InheritableAccountFields, ReadableAccount, WritableAccount,
create_account_shared_data_with_fields as create_account, from_account,
},
solana_accounts_db::{
account_locks::validate_account_locks,
account_storage_entry::AccountStorageEntry,
accounts::{AccountAddressFilter, Accounts, PubkeyAccountSlot},
accounts_db::{AccountsDb, AccountsDbConfig},
accounts_hash::AccountsLtHash,
accounts_index::{IndexKey, ScanConfig, ScanResult},
accounts_update_notifier_interface::AccountsUpdateNotifier,
ancestors::Ancestors,
blockhash_queue::BlockhashQueue,
storable_accounts::StorableAccounts,
utils::create_account_shared_data,
},
solana_builtins::{BUILTINS, STATELESS_BUILTINS},
solana_clock::{
BankId, Epoch, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY,
Slot, SlotIndex, UnixTimestamp,
},
solana_cluster_type::ClusterType,
solana_compute_budget::compute_budget::ComputeBudget,
solana_compute_budget_instruction::instructions_processor::process_compute_budget_instructions,
solana_cost_model::{block_cost_limits::simd_0286_block_limits, cost_tracker::CostTracker},
solana_epoch_info::EpochInfo,
solana_epoch_schedule::EpochSchedule,
solana_feature_gate_interface as feature,
solana_fee::FeeFeatures,
solana_fee_calculator::FeeRateGovernor,
solana_fee_structure::{FeeBudgetLimits, FeeDetails, FeeStructure},
solana_genesis_config::GenesisConfig,
solana_hard_forks::HardForks,
solana_hash::Hash,
solana_inflation::Inflation,
solana_keypair::Keypair,
solana_lattice_hash::lt_hash::LtHash,
solana_measure::{measure::Measure, measure_time, measure_us},
solana_message::{
AccountKeys, SanitizedMessage, VersionedMessage, inner_instruction::InnerInstructions,
},
solana_packet::PACKET_DATA_SIZE,
solana_precompile_error::PrecompileError,
solana_program_runtime::{
invoke_context::BuiltinFunctionWithContext,
loaded_programs::{ProgramCacheEntry, ProgramRuntimeEnvironments},
},
solana_pubkey::{Pubkey, PubkeyHasherBuilder},
solana_rent::Rent,
solana_runtime_transaction::{
runtime_transaction::RuntimeTransaction, transaction_with_meta::TransactionWithMeta,
},
solana_sdk_ids::{bpf_loader_upgradeable, incinerator, native_loader, system_program},
solana_sha256_hasher::hashv,
solana_signature::Signature,
solana_slot_hashes::SlotHashes,
solana_slot_history::{Check, SlotHistory},
solana_stake_interface::{
stake_history::StakeHistory, state::Delegation, sysvar::stake_history,
},
solana_svm::{
account_loader::LoadedTransaction,
account_overrides::AccountOverrides,
program_loader::load_program_with_pubkey,
transaction_balances::{BalanceCollector, SvmTokenInfo},
transaction_commit_result::{CommittedTransaction, TransactionCommitResult},
transaction_error_metrics::TransactionErrorMetrics,
transaction_execution_result::{
TransactionExecutionDetails, TransactionLoadedAccountsStats,
},
transaction_processing_result::{
ProcessedTransaction, TransactionProcessingResult,
TransactionProcessingResultExtensions,
},
transaction_processor::{
ExecutionRecordingConfig, TransactionBatchProcessor, TransactionLogMessages,
TransactionProcessingConfig, TransactionProcessingEnvironment,
},
},
solana_svm_callback::{AccountState, InvokeContextCallback, TransactionProcessingCallback},
solana_svm_timings::{ExecuteTimingType, ExecuteTimings},
solana_svm_transaction::svm_message::SVMMessage,
solana_system_transaction as system_transaction,
solana_sysvar::{self as sysvar, SysvarSerialize, last_restart_slot::LastRestartSlot},
solana_sysvar_id::SysvarId,
solana_time_utils::years_as_slots,
solana_transaction::{
Transaction, TransactionVerificationMode,
sanitized::{MAX_TX_ACCOUNT_LOCKS, MessageHash, SanitizedTransaction},
versioned::VersionedTransaction,
},
solana_transaction_context::{
transaction::TransactionReturnData, transaction_accounts::KeyedAccountSharedData,
},
solana_transaction_error::{TransactionError, TransactionResult as Result},
solana_vote::{
vote_account::{VoteAccount, VoteAccounts, VoteAccountsHashMap},
vote_parser,
},
solana_vote_interface::state::VoteStateV4,
std::{
collections::{HashMap, HashSet},
fmt,
ops::AddAssign,
path::PathBuf,
slice,
sync::{
Arc, LazyLock, LockResult, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
atomic::{
AtomicBool, AtomicI64, AtomicU64,
Ordering::{self, AcqRel, Acquire, Relaxed},
},
},
time::{Duration, Instant},
},
};
#[cfg(feature = "dev-context-only-utils")]
use {
dashmap::DashSet,
rayon::iter::{IntoParallelRefIterator, ParallelIterator},
solana_accounts_db::accounts_db::{
ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS, ACCOUNTS_DB_CONFIG_FOR_TESTING,
},
solana_nonce as nonce,
solana_nonce_account::{SystemAccountKind, get_system_account_kind},
solana_program_runtime::sysvar_cache::SysvarCache,
};
pub use {partitioned_epoch_rewards::KeyedRewardsAndNumPartitions, solana_reward_info::RewardType};
struct VerifyAccountsHashConfig {
require_rooted_bank: bool,
}
mod accounts_lt_hash;
mod address_lookup_table;
pub mod bank_hash_details;
pub mod builtins;
mod check_transactions;
pub mod entry_bytes_budget;
mod fee_distribution;
mod metrics;
pub(crate) mod partitioned_epoch_rewards;
mod recent_blockhashes_account;
mod serde_snapshot;
mod sysvar_cache;
pub(crate) mod tests;
pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0;
pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
pub const MAX_ALPENGLOW_VOTE_ACCOUNTS: usize = 2000;
pub const VAT_TO_BURN_PER_EPOCH: u64 = 1_600_000_000;
static NANOSECOND_CLOCK_ACCOUNT: LazyLock<Pubkey> = LazyLock::new(|| {
let (pubkey, _) =
Pubkey::find_program_address(&[b"alpenclock"], &agave_feature_set::alpenglow::id());
pubkey
});
pub type BankStatusCache = StatusCache<Result<()>>;
#[cfg_attr(
feature = "frozen-abi",
frozen_abi(digest = "23uAyYmzMrmPvPDKf6SvF1YoojYstmEPmdkfAQDnpwsq")
)]
pub type BankSlotDelta = SlotDelta<Result<()>>;
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
pub struct SquashTiming {
pub squash_accounts_ms: u64,
pub squash_accounts_cache_ms: u64,
pub squash_accounts_index_ms: u64,
pub squash_cache_ms: u64,
}
impl AddAssign for SquashTiming {
fn add_assign(&mut self, rhs: Self) {
self.squash_accounts_ms += rhs.squash_accounts_ms;
self.squash_accounts_cache_ms += rhs.squash_accounts_cache_ms;
self.squash_accounts_index_ms += rhs.squash_accounts_index_ms;
self.squash_cache_ms += rhs.squash_cache_ms;
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CollectorFeeDetails {
transaction_fee: u64,
priority_fee: u64,
}
impl CollectorFeeDetails {
pub(crate) fn accumulate(&mut self, fee_details: &FeeDetails) {
self.transaction_fee = self
.transaction_fee
.saturating_add(fee_details.transaction_fee());
self.priority_fee = self
.priority_fee
.saturating_add(fee_details.prioritization_fee());
}
pub fn total_transaction_fee(&self) -> u64 {
self.transaction_fee.saturating_add(self.priority_fee)
}
pub fn total_priority_fee(&self) -> u64 {
self.priority_fee
}
}
impl From<FeeDetails> for CollectorFeeDetails {
fn from(fee_details: FeeDetails) -> Self {
CollectorFeeDetails {
transaction_fee: fee_details.transaction_fee(),
priority_fee: fee_details.prioritization_fee(),
}
}
}
#[derive(Debug)]
pub struct BankRc {
pub accounts: Arc<Accounts>,
pub(crate) parent: RwLock<Option<Arc<Bank>>>,
pub(crate) bank_id_generator: Arc<AtomicU64>,
}
impl BankRc {
pub(crate) fn new(accounts: Accounts) -> Self {
Self {
accounts: Arc::new(accounts),
parent: RwLock::new(None),
bank_id_generator: Arc::new(AtomicU64::new(0)),
}
}
}
pub struct LoadAndExecuteTransactionsOutput {
pub processing_results: Vec<TransactionProcessingResult>,
pub processed_counts: ProcessedTransactionCounts,
pub balance_collector: Option<BalanceCollector>,
}
#[derive(Debug, PartialEq)]
pub struct TransactionSimulationResult {
pub result: Result<()>,
pub logs: TransactionLogMessages,
pub post_simulation_accounts: Vec<KeyedAccountSharedData>,
pub units_consumed: u64,
pub loaded_accounts_data_size: u32,
pub return_data: Option<TransactionReturnData>,
pub inner_instructions: Option<Vec<InnerInstructions>>,
pub fee: Option<u64>,
pub pre_balances: Option<Vec<u64>>,
pub post_balances: Option<Vec<u64>>,
pub pre_token_balances: Option<Vec<SvmTokenInfo>>,
pub post_token_balances: Option<Vec<SvmTokenInfo>>,
}
impl TransactionSimulationResult {
pub fn new_error(err: TransactionError) -> Self {
Self {
fee: None,
inner_instructions: None,
loaded_accounts_data_size: 0,
logs: vec![],
post_balances: None,
post_simulation_accounts: vec![],
post_token_balances: None,
pre_balances: None,
pre_token_balances: None,
result: Err(err),
return_data: None,
units_consumed: 0,
}
}
}
#[derive(Clone, Debug)]
pub struct TransactionBalancesSet {
pub pre_balances: TransactionBalances,
pub post_balances: TransactionBalances,
}
impl TransactionBalancesSet {
pub fn new(pre_balances: TransactionBalances, post_balances: TransactionBalances) -> Self {
assert_eq!(pre_balances.len(), post_balances.len());
Self {
pre_balances,
post_balances,
}
}
}
pub type TransactionBalances = Vec<Vec<u64>>;
pub type PreCommitResult<'a> = Result<Option<RwLockReadGuard<'a, Hash>>>;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
pub enum TransactionLogCollectorFilter {
All,
AllWithVotes,
#[default]
None,
OnlyMentionedAddresses,
}
#[derive(Debug, Default)]
pub struct TransactionLogCollectorConfig {
pub mentioned_addresses: HashSet<Pubkey>,
pub filter: TransactionLogCollectorFilter,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TransactionLogInfo {
pub signature: Signature,
pub result: Result<()>,
pub is_vote: bool,
pub log_messages: TransactionLogMessages,
}
#[derive(Default, Debug)]
pub struct TransactionLogCollector {
pub logs: Vec<TransactionLogInfo>,
pub mentioned_address_map: HashMap<Pubkey, Vec<usize>>,
}
impl TransactionLogCollector {
pub fn get_logs_for_address(
&self,
address: Option<&Pubkey>,
) -> Option<Vec<TransactionLogInfo>> {
match address {
None => Some(self.logs.clone()),
Some(address) => self.mentioned_address_map.get(address).map(|log_indices| {
log_indices
.iter()
.filter_map(|i| self.logs.get(*i).cloned())
.collect()
}),
}
}
}
#[derive(Clone, Debug)]
pub struct BankFieldsToDeserialize {
pub(crate) blockhash_queue: BlockhashQueue,
pub(crate) hash: Hash,
pub(crate) parent_hash: Hash,
pub(crate) parent_slot: Slot,
pub(crate) hard_forks: HardForks,
pub(crate) transaction_count: u64,
pub(crate) tick_height: u64,
pub(crate) signature_count: u64,
pub(crate) capitalization: u64,
pub(crate) max_tick_height: u64,
pub(crate) hashes_per_tick: Option<u64>,
pub(crate) ticks_per_slot: u64,
pub(crate) ns_per_slot: u128,
pub(crate) genesis_creation_time: UnixTimestamp,
pub(crate) slots_per_year: f64,
pub(crate) slot: Slot,
pub(crate) block_height: u64,
pub(crate) leader_id: Pubkey,
pub(crate) fee_rate_governor: FeeRateGovernor,
pub(crate) epoch_schedule: EpochSchedule,
pub(crate) inflation: Inflation,
pub(crate) stakes: DeserializableStakes<Delegation>,
pub(crate) versioned_epoch_stakes: Vec<(Epoch, DeserializableVersionedEpochStakes)>,
pub(crate) is_delta: bool,
pub(crate) accounts_data_len: u64,
pub(crate) accounts_lt_hash: AccountsLtHash,
pub(crate) bank_hash_stats: BankHashStats,
}
#[derive(Debug)]
pub struct BankFieldsToSerialize {
pub blockhash_queue: BlockhashQueue,
pub hash: Hash,
pub parent_hash: Hash,
pub parent_slot: Slot,
pub hard_forks: HardForks,
pub transaction_count: u64,
pub tick_height: u64,
pub signature_count: u64,
pub capitalization: u64,
pub max_tick_height: u64,
pub hashes_per_tick: Option<u64>,
pub ticks_per_slot: u64,
pub ns_per_slot: u128,
pub genesis_creation_time: UnixTimestamp,
pub slots_per_year: f64,
pub slot: Slot,
pub epoch: Epoch,
pub block_height: u64,
pub leader_id: Pubkey,
pub fee_rate_governor: FeeRateGovernor,
pub rent_collector: RentCollector,
pub epoch_schedule: EpochSchedule,
pub inflation: Inflation,
pub stakes: Stakes<StakeAccount<Delegation>>,
pub is_delta: bool,
pub accounts_data_len: u64,
pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
pub accounts_lt_hash: AccountsLtHash,
}
#[cfg(feature = "dev-context-only-utils")]
impl PartialEq for Bank {
fn eq(&self, other: &Self) -> bool {
if std::ptr::eq(self, other) {
return true;
}
#[rustfmt::skip]
let Self {
rc: _,
status_cache: _,
blockhash_queue,
ancestors: _,
hash,
parent_hash,
parent_slot,
hard_forks,
transaction_count,
non_vote_transaction_count_since_restart: _,
transaction_error_count: _,
transaction_entries_count: _,
transactions_per_entry_max: _,
entry_bytes_consumed: _,
tick_height,
signature_count,
capitalization,
max_tick_height,
hashes_per_tick,
ticks_per_slot,
ns_per_slot,
genesis_creation_time,
slots_per_year,
slot,
bank_id: _,
epoch,
block_height,
leader_id,
fee_rate_governor,
rent_collector,
epoch_schedule,
inflation,
stakes_cache,
epoch_stakes,
is_delta,
#[cfg(feature = "dev-context-only-utils")]
hash_overrides,
accounts_lt_hash,
rewards: _,
cluster_type: _,
transaction_debug_keys: _,
transaction_log_collector_config: _,
transaction_log_collector: _,
feature_set: _,
reserved_account_keys: _,
drop_callback: _,
freeze_started: _,
vote_only_bank: _,
cost_tracker: _,
accounts_data_size_initial: _,
accounts_data_size_delta_on_chain: _,
accounts_data_size_delta_off_chain: _,
epoch_reward_status: _,
transaction_processor: _,
check_program_deployment_slot: _,
collector_fee_details: _,
compute_budget: _,
transaction_account_lock_limit: _,
fee_structure: _,
cache_for_accounts_lt_hash: _,
stats_for_accounts_lt_hash: _,
block_id,
expected_bank_hash: _,
bank_hash_stats: _,
epoch_rewards_calculation_cache: _,
} = self;
*blockhash_queue.read().unwrap() == *other.blockhash_queue.read().unwrap()
&& *hash.read().unwrap() == *other.hash.read().unwrap()
&& parent_hash == &other.parent_hash
&& parent_slot == &other.parent_slot
&& *hard_forks.read().unwrap() == *other.hard_forks.read().unwrap()
&& transaction_count.load(Relaxed) == other.transaction_count.load(Relaxed)
&& tick_height.load(Relaxed) == other.tick_height.load(Relaxed)
&& signature_count.load(Relaxed) == other.signature_count.load(Relaxed)
&& capitalization.load(Relaxed) == other.capitalization.load(Relaxed)
&& max_tick_height == &other.max_tick_height
&& hashes_per_tick == &other.hashes_per_tick
&& ticks_per_slot == &other.ticks_per_slot
&& ns_per_slot == &other.ns_per_slot
&& genesis_creation_time == &other.genesis_creation_time
&& slots_per_year == &other.slots_per_year
&& slot == &other.slot
&& epoch == &other.epoch
&& block_height == &other.block_height
&& leader_id == &other.leader_id
&& fee_rate_governor == &other.fee_rate_governor
&& rent_collector == &other.rent_collector
&& epoch_schedule == &other.epoch_schedule
&& *inflation.read().unwrap() == *other.inflation.read().unwrap()
&& *stakes_cache.stakes() == *other.stakes_cache.stakes()
&& epoch_stakes == &other.epoch_stakes
&& is_delta.load(Relaxed) == other.is_delta.load(Relaxed)
&& (Arc::ptr_eq(hash_overrides, &other.hash_overrides) ||
*hash_overrides.lock().unwrap() == *other.hash_overrides.lock().unwrap())
&& *accounts_lt_hash.lock().unwrap() == *other.accounts_lt_hash.lock().unwrap()
&& *block_id.read().unwrap() == *other.block_id.read().unwrap()
}
}
#[cfg(feature = "dev-context-only-utils")]
impl BankFieldsToSerialize {
pub fn default_for_tests() -> Self {
Self {
blockhash_queue: BlockhashQueue::default(),
hash: Hash::default(),
parent_hash: Hash::default(),
parent_slot: Slot::default(),
hard_forks: HardForks::default(),
transaction_count: u64::default(),
tick_height: u64::default(),
signature_count: u64::default(),
capitalization: u64::default(),
max_tick_height: u64::default(),
hashes_per_tick: Option::default(),
ticks_per_slot: u64::default(),
ns_per_slot: u128::default(),
genesis_creation_time: UnixTimestamp::default(),
slots_per_year: f64::default(),
slot: Slot::default(),
epoch: Epoch::default(),
block_height: u64::default(),
leader_id: Pubkey::default(),
fee_rate_governor: FeeRateGovernor::default(),
rent_collector: RentCollector::default(),
epoch_schedule: EpochSchedule::default(),
inflation: Inflation::default(),
stakes: Stakes::<StakeAccount<Delegation>>::default(),
is_delta: bool::default(),
accounts_data_len: u64::default(),
versioned_epoch_stakes: HashMap::default(),
accounts_lt_hash: AccountsLtHash(LtHash([0x7E57; LtHash::NUM_ELEMENTS])),
}
}
}
#[derive(Debug)]
pub enum RewardCalculationEvent<'a, 'b> {
Staking(&'a Pubkey, &'b InflationPointCalculationEvent),
}
const MAX_ENTRY_BYTES_PER_SLOT: u64 = 20 * 1024 * 1024;
pub trait RewardCalcTracer: Fn(&RewardCalculationEvent) + Send + Sync {}
impl<T: Fn(&RewardCalculationEvent) + Send + Sync> RewardCalcTracer for T {}
fn null_tracer() -> Option<impl RewardCalcTracer> {
None::<fn(&RewardCalculationEvent)>
}
pub trait DropCallback: fmt::Debug {
fn callback(&self, b: &Bank);
fn clone_box(&self) -> Box<dyn DropCallback + Send + Sync>;
}
#[derive(Debug, Default)]
pub struct OptionalDropCallback(Option<Box<dyn DropCallback + Send + Sync>>);
#[derive(Default, Debug, Clone, PartialEq)]
#[cfg(feature = "dev-context-only-utils")]
pub struct HashOverrides {
hashes: HashMap<Slot, HashOverride>,
}
#[cfg(feature = "dev-context-only-utils")]
impl HashOverrides {
fn get_hash_override(&self, slot: Slot) -> Option<&HashOverride> {
self.hashes.get(&slot)
}
fn get_blockhash_override(&self, slot: Slot) -> Option<&Hash> {
self.get_hash_override(slot)
.map(|hash_override| &hash_override.blockhash)
}
fn get_bank_hash_override(&self, slot: Slot) -> Option<&Hash> {
self.get_hash_override(slot)
.map(|hash_override| &hash_override.bank_hash)
}
pub fn add_override(&mut self, slot: Slot, blockhash: Hash, bank_hash: Hash) {
let is_new = self
.hashes
.insert(
slot,
HashOverride {
blockhash,
bank_hash,
},
)
.is_none();
assert!(is_new);
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg(feature = "dev-context-only-utils")]
struct HashOverride {
blockhash: Hash,
bank_hash: Hash,
}
pub struct Bank {
pub rc: BankRc,
pub status_cache: Arc<RwLock<BankStatusCache>>,
blockhash_queue: RwLock<BlockhashQueue>,
pub ancestors: Ancestors,
hash: RwLock<Hash>,
parent_hash: Hash,
parent_slot: Slot,
hard_forks: Arc<RwLock<HardForks>>,
transaction_count: AtomicU64,
non_vote_transaction_count_since_restart: AtomicU64,
transaction_error_count: AtomicU64,
transaction_entries_count: AtomicU64,
transactions_per_entry_max: AtomicU64,
entry_bytes_consumed: EntryBytesBudget,
tick_height: AtomicU64,
signature_count: AtomicU64,
capitalization: AtomicU64,
max_tick_height: u64,
hashes_per_tick: Option<u64>,
ticks_per_slot: u64,
pub ns_per_slot: u128,
genesis_creation_time: UnixTimestamp,
slots_per_year: f64,
slot: Slot,
bank_id: BankId,
epoch: Epoch,
block_height: u64,
leader_id: Pubkey,
pub(crate) fee_rate_governor: FeeRateGovernor,
rent_collector: RentCollector,
pub(crate) epoch_schedule: EpochSchedule,
inflation: Arc<RwLock<Inflation>>,
stakes_cache: StakesCache,
epoch_stakes: HashMap<Epoch, VersionedEpochStakes>,
is_delta: AtomicBool,
pub rewards: RwLock<Vec<(Pubkey, RewardInfo)>>,
pub cluster_type: Option<ClusterType>,
transaction_debug_keys: Option<Arc<HashSet<Pubkey>>>,
pub transaction_log_collector_config: Arc<RwLock<TransactionLogCollectorConfig>>,
pub transaction_log_collector: Arc<RwLock<TransactionLogCollector>>,
pub feature_set: Arc<FeatureSet>,
reserved_account_keys: Arc<ReservedAccountKeys>,
pub drop_callback: RwLock<OptionalDropCallback>,
pub freeze_started: AtomicBool,
vote_only_bank: bool,
cost_tracker: RwLock<CostTracker>,
accounts_data_size_initial: u64,
accounts_data_size_delta_on_chain: AtomicI64,
accounts_data_size_delta_off_chain: AtomicI64,
epoch_reward_status: EpochRewardStatus,
transaction_processor: TransactionBatchProcessor<BankForks>,
check_program_deployment_slot: bool,
collector_fee_details: RwLock<CollectorFeeDetails>,
compute_budget: Option<ComputeBudget>,
transaction_account_lock_limit: Option<usize>,
fee_structure: FeeStructure,
#[cfg(feature = "dev-context-only-utils")]
hash_overrides: Arc<Mutex<HashOverrides>>,
accounts_lt_hash: Mutex<AccountsLtHash>,
cache_for_accounts_lt_hash: DashMap<Pubkey, AccountsLtHashCacheValue, ahash::RandomState>,
stats_for_accounts_lt_hash: AccountsLtHashStats,
block_id: RwLock<Option<Hash>>,
expected_bank_hash: RwLock<Option<Hash>>,
bank_hash_stats: AtomicBankHashStats,
epoch_rewards_calculation_cache: Arc<Mutex<HashMap<Hash, Arc<PartitionedRewardsCalculation>>>>,
}
#[derive(Debug)]
struct RewardCommission {
commission_account: AccountSharedData,
commission_bps: u16,
commission_lamports: u64,
}
type RewardCommissions = HashMap<Pubkey, RewardCommission, PubkeyHasherBuilder>;
#[derive(Debug, Default)]
pub struct NewBankOptions {
pub vote_only_bank: bool,
}
#[cfg(feature = "dev-context-only-utils")]
#[derive(Debug)]
pub struct BankTestConfig {
pub accounts_db_config: AccountsDbConfig,
}
#[cfg(feature = "dev-context-only-utils")]
impl Default for BankTestConfig {
fn default() -> Self {
Self {
accounts_db_config: ACCOUNTS_DB_CONFIG_FOR_TESTING,
}
}
}
struct EpochInflationRewards {
validator_rewards_lamports: u64,
validator_rate: f64,
foundation_rate: f64,
}
#[derive(Debug, Default, PartialEq)]
pub struct ProcessedTransactionCounts {
pub processed_transactions_count: u64,
pub processed_non_vote_transactions_count: u64,
pub processed_with_successful_result_count: u64,
pub signature_count: u64,
}
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct BankHashStats {
pub num_updated_accounts: u64,
pub num_removed_accounts: u64,
pub num_lamports_stored: u64,
pub total_data_len: u64,
pub num_executable_accounts: u64,
}
impl BankHashStats {
pub fn update<T: ReadableAccount>(&mut self, account: &T) {
if account.lamports() == 0 {
self.num_removed_accounts += 1;
} else {
self.num_updated_accounts += 1;
}
self.total_data_len = self
.total_data_len
.wrapping_add(account.data().len() as u64);
if account.executable() {
self.num_executable_accounts += 1;
}
self.num_lamports_stored = self.num_lamports_stored.wrapping_add(account.lamports());
}
pub fn accumulate(&mut self, other: &BankHashStats) {
self.num_updated_accounts += other.num_updated_accounts;
self.num_removed_accounts += other.num_removed_accounts;
self.total_data_len = self.total_data_len.wrapping_add(other.total_data_len);
self.num_lamports_stored = self
.num_lamports_stored
.wrapping_add(other.num_lamports_stored);
self.num_executable_accounts += other.num_executable_accounts;
}
}
#[derive(Debug, Default)]
pub struct AtomicBankHashStats {
pub num_updated_accounts: AtomicU64,
pub num_removed_accounts: AtomicU64,
pub num_lamports_stored: AtomicU64,
pub total_data_len: AtomicU64,
pub num_executable_accounts: AtomicU64,
}
impl AtomicBankHashStats {
pub fn new(stat: &BankHashStats) -> Self {
AtomicBankHashStats {
num_updated_accounts: AtomicU64::new(stat.num_updated_accounts),
num_removed_accounts: AtomicU64::new(stat.num_removed_accounts),
num_lamports_stored: AtomicU64::new(stat.num_lamports_stored),
total_data_len: AtomicU64::new(stat.total_data_len),
num_executable_accounts: AtomicU64::new(stat.num_executable_accounts),
}
}
pub fn accumulate(&self, other: &BankHashStats) {
self.num_updated_accounts
.fetch_add(other.num_updated_accounts, Relaxed);
self.num_removed_accounts
.fetch_add(other.num_removed_accounts, Relaxed);
self.total_data_len.fetch_add(other.total_data_len, Relaxed);
self.num_lamports_stored
.fetch_add(other.num_lamports_stored, Relaxed);
self.num_executable_accounts
.fetch_add(other.num_executable_accounts, Relaxed);
}
pub fn load(&self) -> BankHashStats {
BankHashStats {
num_updated_accounts: self.num_updated_accounts.load(Relaxed),
num_removed_accounts: self.num_removed_accounts.load(Relaxed),
num_lamports_stored: self.num_lamports_stored.load(Relaxed),
total_data_len: self.total_data_len.load(Relaxed),
num_executable_accounts: self.num_executable_accounts.load(Relaxed),
}
}
}
struct NewEpochBundle {
stake_history: CowStakeHistory,
vote_accounts: VoteAccounts,
rewards_calculation: Arc<PartitionedRewardsCalculation>,
calculate_activated_stake_time_us: u64,
update_rewards_with_thread_pool_time_us: u64,
}
impl Bank {
fn default_with_accounts(accounts: Accounts) -> Self {
let mut bank = Self {
rc: BankRc::new(accounts),
status_cache: Arc::<RwLock<BankStatusCache>>::default(),
blockhash_queue: RwLock::<BlockhashQueue>::default(),
ancestors: Ancestors::default(),
hash: RwLock::<Hash>::default(),
parent_hash: Hash::default(),
parent_slot: Slot::default(),
hard_forks: Arc::<RwLock<HardForks>>::default(),
transaction_count: AtomicU64::default(),
non_vote_transaction_count_since_restart: AtomicU64::default(),
transaction_error_count: AtomicU64::default(),
transaction_entries_count: AtomicU64::default(),
transactions_per_entry_max: AtomicU64::default(),
entry_bytes_consumed: EntryBytesBudget::new(MAX_ENTRY_BYTES_PER_SLOT),
tick_height: AtomicU64::default(),
signature_count: AtomicU64::default(),
capitalization: AtomicU64::default(),
max_tick_height: u64::default(),
hashes_per_tick: Option::<u64>::default(),
ticks_per_slot: u64::default(),
ns_per_slot: u128::default(),
genesis_creation_time: UnixTimestamp::default(),
slots_per_year: f64::default(),
slot: Slot::default(),
bank_id: BankId::default(),
epoch: Epoch::default(),
block_height: u64::default(),
leader_id: Pubkey::default(),
fee_rate_governor: FeeRateGovernor::default(),
rent_collector: RentCollector::default(),
epoch_schedule: EpochSchedule::default(),
inflation: Arc::<RwLock<Inflation>>::default(),
stakes_cache: StakesCache::default(),
epoch_stakes: HashMap::<Epoch, VersionedEpochStakes>::default(),
is_delta: AtomicBool::default(),
rewards: RwLock::<Vec<(Pubkey, RewardInfo)>>::default(),
cluster_type: Option::<ClusterType>::default(),
transaction_debug_keys: Option::<Arc<HashSet<Pubkey>>>::default(),
transaction_log_collector_config: Arc::<RwLock<TransactionLogCollectorConfig>>::default(
),
transaction_log_collector: Arc::<RwLock<TransactionLogCollector>>::default(),
feature_set: Arc::<FeatureSet>::default(),
reserved_account_keys: Arc::<ReservedAccountKeys>::default(),
drop_callback: RwLock::new(OptionalDropCallback(None)),
freeze_started: AtomicBool::default(),
vote_only_bank: false,
cost_tracker: RwLock::<CostTracker>::default(),
accounts_data_size_initial: 0,
accounts_data_size_delta_on_chain: AtomicI64::new(0),
accounts_data_size_delta_off_chain: AtomicI64::new(0),
epoch_reward_status: EpochRewardStatus::default(),
transaction_processor: TransactionBatchProcessor::default(),
check_program_deployment_slot: false,
collector_fee_details: RwLock::new(CollectorFeeDetails::default()),
compute_budget: None,
transaction_account_lock_limit: None,
fee_structure: FeeStructure::default(),
#[cfg(feature = "dev-context-only-utils")]
hash_overrides: Arc::new(Mutex::new(HashOverrides::default())),
accounts_lt_hash: Mutex::new(AccountsLtHash(LtHash::identity())),
cache_for_accounts_lt_hash: DashMap::default(),
stats_for_accounts_lt_hash: AccountsLtHashStats::default(),
block_id: RwLock::new(None),
expected_bank_hash: RwLock::new(None),
bank_hash_stats: AtomicBankHashStats::default(),
epoch_rewards_calculation_cache: Arc::new(Mutex::new(HashMap::default())),
};
bank.transaction_processor =
TransactionBatchProcessor::new_uninitialized(bank.slot, bank.epoch);
bank.accounts_data_size_initial = bank.calculate_accounts_data_size().unwrap();
bank
}
#[allow(clippy::too_many_arguments)]
pub fn new_from_genesis(
genesis_config: &GenesisConfig,
runtime_config: Arc<RuntimeConfig>,
paths: Vec<PathBuf>,
debug_keys: Option<Arc<HashSet<Pubkey>>>,
accounts_db_config: AccountsDbConfig,
accounts_update_notifier: Option<AccountsUpdateNotifier>,
#[allow(unused)] leader_id_for_tests: Option<Pubkey>,
exit: Arc<AtomicBool>,
#[allow(unused)] genesis_hash: Option<Hash>,
#[allow(unused)] feature_set: Option<FeatureSet>,
) -> Self {
let accounts_db =
AccountsDb::new_with_config(paths, accounts_db_config, accounts_update_notifier, exit);
let accounts = Accounts::new(Arc::new(accounts_db));
let mut bank = Self::default_with_accounts(accounts);
bank.ancestors = Ancestors::from(vec![bank.slot()]);
bank.compute_budget = runtime_config.compute_budget;
if let Some(compute_budget) = &bank.compute_budget {
bank.transaction_processor
.set_execution_cost(compute_budget.to_cost());
}
bank.transaction_account_lock_limit = runtime_config.transaction_account_lock_limit;
bank.transaction_debug_keys = debug_keys;
bank.cluster_type = Some(genesis_config.cluster_type);
#[cfg(feature = "dev-context-only-utils")]
{
bank.feature_set = Arc::new(feature_set.unwrap_or_default());
}
#[cfg(not(feature = "dev-context-only-utils"))]
bank.process_genesis_config(genesis_config);
#[cfg(feature = "dev-context-only-utils")]
bank.process_genesis_config(genesis_config, leader_id_for_tests, genesis_hash);
bank.compute_and_apply_genesis_features();
{
let stakes = bank.get_top_epoch_stakes();
let stakes = SerdeStakesToStakeFormat::from(stakes);
for epoch in 0..=bank.get_leader_schedule_epoch(bank.slot) {
bank.epoch_stakes
.insert(epoch, VersionedEpochStakes::new(stakes.clone(), epoch));
}
bank.update_stake_history(None);
}
bank.update_clock(None);
bank.update_rent();
bank.update_epoch_schedule();
bank.update_recent_blockhashes();
bank.update_last_restart_slot();
bank.transaction_processor
.fill_missing_sysvar_cache_entries(&bank);
bank
}
pub fn new_from_parent(parent: Arc<Bank>, leader_id: &Pubkey, slot: Slot) -> Self {
Self::_new_from_parent(
parent,
leader_id,
slot,
null_tracer(),
NewBankOptions::default(),
)
}
pub fn new_from_parent_with_options(
parent: Arc<Bank>,
leader_id: &Pubkey,
slot: Slot,
new_bank_options: NewBankOptions,
) -> Self {
Self::_new_from_parent(parent, leader_id, slot, null_tracer(), new_bank_options)
}
pub fn new_from_parent_with_tracer(
parent: Arc<Bank>,
leader_id: &Pubkey,
slot: Slot,
reward_calc_tracer: impl RewardCalcTracer,
) -> Self {
Self::_new_from_parent(
parent,
leader_id,
slot,
Some(reward_calc_tracer),
NewBankOptions::default(),
)
}
fn get_rent_collector_from(rent_collector: &RentCollector, epoch: Epoch) -> RentCollector {
rent_collector.clone_with_epoch(epoch)
}
fn _new_from_parent(
parent: Arc<Bank>,
leader_id: &Pubkey,
slot: Slot,
reward_calc_tracer: Option<impl RewardCalcTracer>,
new_bank_options: NewBankOptions,
) -> Self {
let mut time = Measure::start("bank::new_from_parent");
let NewBankOptions { vote_only_bank } = new_bank_options;
parent.freeze();
assert_ne!(slot, parent.slot());
let epoch_schedule = parent.epoch_schedule().clone();
let epoch = epoch_schedule.get_epoch(slot);
let (rc, bank_rc_creation_time_us) = measure_us!({
let accounts_db = Arc::clone(&parent.rc.accounts.accounts_db);
BankRc {
accounts: Arc::new(Accounts::new(accounts_db)),
parent: RwLock::new(Some(Arc::clone(&parent))),
bank_id_generator: Arc::clone(&parent.rc.bank_id_generator),
}
});
let (status_cache, status_cache_time_us) = measure_us!(Arc::clone(&parent.status_cache));
let (fee_rate_governor, fee_components_time_us) = measure_us!(
FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count())
);
let bank_id = rc.bank_id_generator.fetch_add(1, Relaxed) + 1;
let (blockhash_queue, blockhash_queue_time_us) =
measure_us!(RwLock::new(parent.blockhash_queue.read().unwrap().clone()));
let (stakes_cache, stakes_cache_time_us) =
measure_us!(StakesCache::new(parent.stakes_cache.stakes().clone()));
let (epoch_stakes, epoch_stakes_time_us) = measure_us!(parent.epoch_stakes.clone());
let (transaction_processor, builtin_program_ids_time_us) = measure_us!(
TransactionBatchProcessor::new_from(&parent.transaction_processor, slot, epoch)
);
let (transaction_debug_keys, transaction_debug_keys_time_us) =
measure_us!(parent.transaction_debug_keys.clone());
let (transaction_log_collector_config, transaction_log_collector_config_time_us) =
measure_us!(parent.transaction_log_collector_config.clone());
let (feature_set, feature_set_time_us) = measure_us!(parent.feature_set.clone());
let accounts_data_size_initial = parent.load_accounts_data_size();
let mut new = Self {
rc,
status_cache,
slot,
bank_id,
epoch,
blockhash_queue,
hashes_per_tick: parent.hashes_per_tick,
ticks_per_slot: parent.ticks_per_slot,
ns_per_slot: parent.ns_per_slot,
genesis_creation_time: parent.genesis_creation_time,
slots_per_year: parent.slots_per_year,
epoch_schedule,
rent_collector: Self::get_rent_collector_from(&parent.rent_collector, epoch),
max_tick_height: slot
.checked_add(1)
.expect("max tick height addition overflowed")
.checked_mul(parent.ticks_per_slot)
.expect("max tick height multiplication overflowed"),
block_height: parent
.block_height
.checked_add(1)
.expect("block height addition overflowed"),
fee_rate_governor,
capitalization: AtomicU64::new(parent.capitalization()),
vote_only_bank,
inflation: parent.inflation.clone(),
transaction_count: AtomicU64::new(parent.transaction_count()),
non_vote_transaction_count_since_restart: AtomicU64::new(
parent.non_vote_transaction_count_since_restart(),
),
transaction_error_count: AtomicU64::new(0),
transaction_entries_count: AtomicU64::new(0),
transactions_per_entry_max: AtomicU64::new(0),
entry_bytes_consumed: EntryBytesBudget::new(parent.entry_bytes_budget().slot_limit()),
stakes_cache,
epoch_stakes,
parent_hash: parent.hash(),
parent_slot: parent.slot(),
leader_id: *leader_id,
ancestors: Ancestors::default(),
hash: RwLock::new(Hash::default()),
is_delta: AtomicBool::new(false),
tick_height: AtomicU64::new(parent.tick_height.load(Relaxed)),
signature_count: AtomicU64::new(0),
hard_forks: parent.hard_forks.clone(),
rewards: RwLock::new(vec![]),
cluster_type: parent.cluster_type,
transaction_debug_keys,
transaction_log_collector_config,
transaction_log_collector: Arc::new(RwLock::new(TransactionLogCollector::default())),
feature_set: Arc::clone(&feature_set),
reserved_account_keys: parent.reserved_account_keys.clone(),
drop_callback: RwLock::new(OptionalDropCallback(
parent
.drop_callback
.read()
.unwrap()
.0
.as_ref()
.map(|drop_callback| drop_callback.clone_box()),
)),
freeze_started: AtomicBool::new(false),
cost_tracker: RwLock::new(parent.read_cost_tracker().unwrap().new_from_parent_limits()),
accounts_data_size_initial,
accounts_data_size_delta_on_chain: AtomicI64::new(0),
accounts_data_size_delta_off_chain: AtomicI64::new(0),
epoch_reward_status: parent.epoch_reward_status.clone(),
transaction_processor,
check_program_deployment_slot: false,
collector_fee_details: RwLock::new(CollectorFeeDetails::default()),
compute_budget: parent.compute_budget,
transaction_account_lock_limit: parent.transaction_account_lock_limit,
fee_structure: parent.fee_structure.clone(),
#[cfg(feature = "dev-context-only-utils")]
hash_overrides: parent.hash_overrides.clone(),
accounts_lt_hash: Mutex::new(parent.accounts_lt_hash.lock().unwrap().clone()),
cache_for_accounts_lt_hash: DashMap::default(),
stats_for_accounts_lt_hash: AccountsLtHashStats::default(),
block_id: RwLock::new(None),
expected_bank_hash: RwLock::new(None),
bank_hash_stats: AtomicBankHashStats::default(),
epoch_rewards_calculation_cache: parent.epoch_rewards_calculation_cache.clone(),
};
let (_, ancestors_time_us) = measure_us!({
let mut ancestors = Vec::with_capacity(1 + new.parents().len());
ancestors.push(new.slot());
new.parents().iter().for_each(|p| {
ancestors.push(p.slot());
});
new.ancestors = Ancestors::from(ancestors);
});
let (_, update_epoch_time_us) = measure_us!({
if parent.epoch() < new.epoch() {
new.process_new_epoch(
parent.epoch(),
parent.slot(),
parent.block_height(),
reward_calc_tracer,
);
} else {
let leader_schedule_epoch = new.epoch_schedule().get_leader_schedule_epoch(slot);
new.update_epoch_stakes(leader_schedule_epoch);
}
new.distribute_partitioned_epoch_rewards();
});
let (_, cache_preparation_time_us) =
measure_us!(new.prepare_program_cache_for_upcoming_feature_set());
let (_, update_sysvars_time_us) = measure_us!({
new.update_slot_hashes();
new.update_stake_history(Some(parent.epoch()));
new.update_clock(Some(parent.epoch()));
new.update_last_restart_slot()
});
let (_, fill_sysvar_cache_time_us) = measure_us!(
new.transaction_processor
.fill_missing_sysvar_cache_entries(&new)
);
let (num_accounts_modified_this_slot, populate_cache_for_accounts_lt_hash_us) =
measure_us!({
let accounts_modified_this_slot =
new.rc.accounts.accounts_db.get_pubkeys_for_slot(slot);
let num_accounts_modified_this_slot = accounts_modified_this_slot.len();
for pubkey in accounts_modified_this_slot {
new.cache_for_accounts_lt_hash
.entry(pubkey)
.or_insert(AccountsLtHashCacheValue::BankNew);
}
num_accounts_modified_this_slot
});
time.stop();
report_new_bank_metrics(
slot,
parent.slot(),
new.block_height,
num_accounts_modified_this_slot,
NewBankTimings {
bank_rc_creation_time_us,
total_elapsed_time_us: time.as_us(),
status_cache_time_us,
fee_components_time_us,
blockhash_queue_time_us,
stakes_cache_time_us,
epoch_stakes_time_us,
builtin_program_ids_time_us,
executor_cache_time_us: 0,
transaction_debug_keys_time_us,
transaction_log_collector_config_time_us,
feature_set_time_us,
ancestors_time_us,
update_epoch_time_us,
cache_preparation_time_us,
update_sysvars_time_us,
fill_sysvar_cache_time_us,
populate_cache_for_accounts_lt_hash_us,
},
);
report_loaded_programs_stats(
&parent
.transaction_processor
.global_program_cache
.read()
.unwrap()
.stats,
parent.slot(),
);
new.transaction_processor
.global_program_cache
.write()
.unwrap()
.stats
.reset();
new
}
pub fn set_fork_graph_in_program_cache(&self, fork_graph: Weak<RwLock<BankForks>>) {
self.transaction_processor
.global_program_cache
.write()
.unwrap()
.set_fork_graph(fork_graph);
}
fn prepare_program_cache_for_upcoming_feature_set(&self) {
let (_epoch, slot_index) = self.epoch_schedule.get_epoch_and_slot_index(self.slot);
let slots_in_epoch = self.epoch_schedule.get_slots_in_epoch(self.epoch);
let (upcoming_feature_set, _newly_activated) = self.compute_active_feature_set(true);
let slots_in_recompilation_phase =
(solana_program_runtime::loaded_programs::MAX_LOADED_ENTRY_COUNT as u64)
.min(slots_in_epoch)
.checked_div(2)
.unwrap();
let program_cache = self
.transaction_processor
.global_program_cache
.read()
.unwrap();
let mut epoch_boundary_preparation = self
.transaction_processor
.epoch_boundary_preparation
.write()
.unwrap();
if let Some(upcoming_environments) =
epoch_boundary_preparation.upcoming_environments.as_ref()
{
let upcoming_environments = upcoming_environments.clone();
if let Some((key, program_to_recompile)) =
epoch_boundary_preparation.programs_to_recompile.pop()
{
drop(epoch_boundary_preparation);
drop(program_cache);
if let Some((recompiled, last_modification_slot)) = load_program_with_pubkey(
self,
&upcoming_environments,
&key,
self.slot,
&mut ExecuteTimings::default(),
) {
recompiled.tx_usage_counter.fetch_add(
program_to_recompile
.tx_usage_counter
.load(Ordering::Relaxed),
Ordering::Relaxed,
);
let mut program_cache = self
.transaction_processor
.global_program_cache
.write()
.unwrap();
program_cache.assign_program(
&upcoming_environments,
key,
last_modification_slot,
recompiled,
);
}
}
} else if slot_index.saturating_add(slots_in_recompilation_phase) >= slots_in_epoch {
let new_environments = self.create_program_runtime_environments(&upcoming_feature_set);
let mut upcoming_environments = self.transaction_processor.environments.clone();
let changed_program_runtime_v1 =
*upcoming_environments.program_runtime_v1 != *new_environments.program_runtime_v1;
let changed_program_runtime_v2 =
*upcoming_environments.program_runtime_v2 != *new_environments.program_runtime_v2;
if changed_program_runtime_v1 {
upcoming_environments.program_runtime_v1 = new_environments.program_runtime_v1;
}
if changed_program_runtime_v2 {
upcoming_environments.program_runtime_v2 = new_environments.program_runtime_v2;
}
epoch_boundary_preparation.upcoming_epoch = self.epoch.saturating_add(1);
epoch_boundary_preparation.upcoming_environments = Some(upcoming_environments);
epoch_boundary_preparation.programs_to_recompile = program_cache
.get_flattened_entries(changed_program_runtime_v1, changed_program_runtime_v2)
.into_iter()
.map(|(id, _last_modification_slot, entry)| (id, entry))
.collect();
epoch_boundary_preparation
.programs_to_recompile
.sort_by_cached_key(|(_id, program)| program.decayed_usage_counter(self.slot));
}
}
pub fn prune_program_cache(&self, new_root_slot: Slot, new_root_epoch: Epoch) {
let upcoming_environments = self
.transaction_processor
.epoch_boundary_preparation
.write()
.unwrap()
.reroot(new_root_epoch);
self.transaction_processor
.global_program_cache
.write()
.unwrap()
.prune(new_root_slot, upcoming_environments);
}
pub fn prune_program_cache_by_deployment_slot(&self, deployment_slot: Slot) {
self.transaction_processor
.global_program_cache
.write()
.unwrap()
.prune_by_deployment_slot(deployment_slot);
}
pub fn new_warmup_cooldown_rate_epoch(&self) -> Option<Epoch> {
self.feature_set
.new_warmup_cooldown_rate_epoch(&self.epoch_schedule)
}
fn get_cached_vote_accounts<'a>(
&'a self,
rewarded_epoch: Epoch,
distribution_epoch_vote_accounts: &'a VoteAccounts,
) -> CachedVoteAccounts<'a> {
let snapshot_epoch_vote_accounts = self
.epoch_stakes(rewarded_epoch)
.map(|epoch_stakes| epoch_stakes.stakes().vote_accounts());
let rewarded_epoch_vote_accounts = self
.epoch_stakes(self.epoch())
.map(|epoch_stakes| epoch_stakes.stakes().vote_accounts());
CachedVoteAccounts {
snapshot_epoch_vote_accounts,
rewarded_epoch_vote_accounts,
distribution_epoch_vote_accounts,
}
}
fn compute_new_epoch_caches_and_rewards(
&self,
thread_pool: &ThreadPool,
rewarded_epoch: Epoch,
reward_calc_tracer: Option<impl RewardCalcTracer>,
rewards_metrics: &mut RewardsMetrics,
) -> NewEpochBundle {
let stakes = self.stakes_cache.stakes();
let stake_delegations = stakes.stake_delegations_vec();
let ((stake_history, vote_accounts), calculate_activated_stake_time_us) =
measure_us!(stakes.calculate_activated_stake(
self.epoch(),
thread_pool,
self.new_warmup_cooldown_rate_epoch(),
&stake_delegations
));
let distribution_epoch_vote_accounts =
self.maybe_filter_vote_accounts_for_vat(&vote_accounts);
let cached_vote_accounts =
self.get_cached_vote_accounts(rewarded_epoch, &distribution_epoch_vote_accounts);
let (rewards_calculation, update_rewards_with_thread_pool_time_us) =
measure_us!(self.calculate_rewards(
&stake_history,
stake_delegations,
cached_vote_accounts,
rewarded_epoch,
reward_calc_tracer,
thread_pool,
rewards_metrics,
));
NewEpochBundle {
stake_history,
vote_accounts,
rewards_calculation,
calculate_activated_stake_time_us,
update_rewards_with_thread_pool_time_us,
}
}
fn process_new_epoch(
&mut self,
parent_epoch: Epoch,
parent_slot: Slot,
parent_height: u64,
reward_calc_tracer: Option<impl RewardCalcTracer>,
) {
let epoch = self.epoch();
let slot = self.slot();
let (thread_pool, thread_pool_time_us) = measure_us!(
ThreadPoolBuilder::new()
.thread_name(|i| format!("solBnkNewEpch{i:02}"))
.build()
.expect("new rayon threadpool")
);
let (_, apply_feature_activations_time_us) = measure_us!(
thread_pool.install(|| { self.compute_and_apply_new_feature_activations() })
);
let mut rewards_metrics = RewardsMetrics::default();
let NewEpochBundle {
stake_history,
vote_accounts,
rewards_calculation,
calculate_activated_stake_time_us,
update_rewards_with_thread_pool_time_us,
} = self.compute_new_epoch_caches_and_rewards(
&thread_pool,
parent_epoch,
reward_calc_tracer,
&mut rewards_metrics,
);
self.stakes_cache
.activate_epoch(epoch, stake_history, vote_accounts);
let leader_schedule_epoch = self.epoch_schedule.get_leader_schedule_epoch(slot);
let (_, update_epoch_stakes_time_us) =
measure_us!(self.update_epoch_stakes(leader_schedule_epoch));
self.begin_partitioned_rewards(
parent_epoch,
parent_slot,
parent_height,
&rewards_calculation,
&rewards_metrics,
);
report_new_epoch_metrics(
epoch,
slot,
parent_slot,
NewEpochTimings {
thread_pool_time_us,
apply_feature_activations_time_us,
calculate_activated_stake_time_us,
update_epoch_stakes_time_us,
update_rewards_with_thread_pool_time_us,
},
rewards_metrics,
);
let new_environments = self.create_program_runtime_environments(&self.feature_set);
self.transaction_processor
.set_environments(new_environments);
}
pub fn byte_limit_for_scans(&self) -> Option<usize> {
self.rc
.accounts
.accounts_db
.accounts_index
.scan_results_limit_bytes
}
pub fn proper_ancestors_set(&self) -> HashSet<Slot> {
HashSet::from_iter(self.proper_ancestors())
}
pub(crate) fn proper_ancestors(&self) -> impl Iterator<Item = Slot> + '_ {
self.ancestors
.keys()
.into_iter()
.filter(move |slot| *slot != self.slot)
}
pub fn set_callback(&self, callback: Option<Box<dyn DropCallback + Send + Sync>>) {
*self.drop_callback.write().unwrap() = OptionalDropCallback(callback);
}
pub fn vote_only_bank(&self) -> bool {
self.vote_only_bank
}
pub fn warp_from_parent(parent: Arc<Bank>, leader_id: &Pubkey, slot: Slot) -> Self {
parent.freeze();
let parent_timestamp = parent.clock().unix_timestamp;
let mut new = Bank::new_from_parent(parent, leader_id, slot);
new.update_epoch_stakes(new.epoch_schedule().get_epoch(slot));
new.tick_height.store(new.max_tick_height(), Relaxed);
let mut clock = new.clock();
clock.epoch_start_timestamp = parent_timestamp;
clock.unix_timestamp = parent_timestamp;
new.update_sysvar_account(&sysvar::clock::id(), |account| {
create_account(
&clock,
new.inherit_specially_retained_account_fields(account),
)
});
new.transaction_processor
.fill_missing_sysvar_cache_entries(&new);
new.freeze();
new
}
pub(crate) fn new_from_snapshot(
bank_rc: BankRc,
genesis_config: &GenesisConfig,
runtime_config: Arc<RuntimeConfig>,
fields: BankFieldsToDeserialize,
debug_keys: Option<Arc<HashSet<Pubkey>>>,
accounts_data_size_initial: u64,
epoch_stakes: HashMap<Epoch, VersionedEpochStakes>,
) -> Self {
let now = Instant::now();
let slot = fields.slot;
let epoch = fields.epoch_schedule.get_epoch(slot);
let ancestors = Ancestors::from(vec![slot]);
let (stakes, stakes_time) = measure_time!(
Stakes::load_from_deserialized_delegations(fields.stakes, |pubkey| {
let (account, _slot) = bank_rc
.accounts
.load_with_fixed_root_do_not_populate_read_cache(&ancestors, pubkey)?;
Some(account)
})
.expect(
"Stakes cache is inconsistent with accounts-db. This can indicate a corrupted \
snapshot or bugs in cached accounts or accounts-db.",
)
);
info!("Loading Stakes took: {stakes_time}");
assert!(
fields.versioned_epoch_stakes.is_empty(),
"should be already converted and passed in epoch_stakes parameter"
);
assert!(
!epoch_stakes.is_empty(),
"should be populated (from fields.versioned_epoch_stakes)"
);
let stakes_accounts_load_duration = now.elapsed();
let rent = {
let rent_sysvar = bank_rc
.accounts
.load_with_fixed_root_do_not_populate_read_cache(&ancestors, &sysvar::rent::id())
.expect("snapshot must contain rent sysvar account")
.0;
from_account::<sysvar::rent::Rent, _>(&rent_sysvar)
.expect("snapshot must contain well-formed rent sysvar account")
};
let mut bank = Self {
rc: bank_rc,
status_cache: Arc::<RwLock<BankStatusCache>>::default(),
blockhash_queue: RwLock::new(fields.blockhash_queue),
ancestors,
hash: RwLock::new(fields.hash),
parent_hash: fields.parent_hash,
parent_slot: fields.parent_slot,
hard_forks: Arc::new(RwLock::new(fields.hard_forks)),
transaction_count: AtomicU64::new(fields.transaction_count),
non_vote_transaction_count_since_restart: AtomicU64::default(),
transaction_error_count: AtomicU64::default(),
transaction_entries_count: AtomicU64::default(),
transactions_per_entry_max: AtomicU64::default(),
entry_bytes_consumed: EntryBytesBudget::new(MAX_ENTRY_BYTES_PER_SLOT),
tick_height: AtomicU64::new(fields.tick_height),
signature_count: AtomicU64::new(fields.signature_count),
capitalization: AtomicU64::new(fields.capitalization),
max_tick_height: fields.max_tick_height,
hashes_per_tick: fields.hashes_per_tick,
ticks_per_slot: fields.ticks_per_slot,
ns_per_slot: fields.ns_per_slot,
genesis_creation_time: fields.genesis_creation_time,
slots_per_year: fields.slots_per_year,
slot,
bank_id: 0,
epoch,
block_height: fields.block_height,
leader_id: fields.leader_id,
fee_rate_governor: fields.fee_rate_governor,
rent_collector: RentCollector::new(
epoch,
fields.epoch_schedule.clone(),
fields.slots_per_year,
rent,
),
epoch_schedule: fields.epoch_schedule,
inflation: Arc::new(RwLock::new(fields.inflation)),
stakes_cache: StakesCache::new(stakes),
epoch_stakes,
is_delta: AtomicBool::new(fields.is_delta),
rewards: RwLock::new(vec![]),
cluster_type: Some(genesis_config.cluster_type),
transaction_debug_keys: debug_keys,
transaction_log_collector_config: Arc::<RwLock<TransactionLogCollectorConfig>>::default(
),
transaction_log_collector: Arc::<RwLock<TransactionLogCollector>>::default(),
feature_set: Arc::<FeatureSet>::default(),
reserved_account_keys: Arc::<ReservedAccountKeys>::default(),
drop_callback: RwLock::new(OptionalDropCallback(None)),
freeze_started: AtomicBool::new(fields.hash != Hash::default()),
vote_only_bank: false,
cost_tracker: RwLock::new(CostTracker::default()),
accounts_data_size_initial,
accounts_data_size_delta_on_chain: AtomicI64::new(0),
accounts_data_size_delta_off_chain: AtomicI64::new(0),
epoch_reward_status: EpochRewardStatus::default(),
transaction_processor: TransactionBatchProcessor::default(),
check_program_deployment_slot: false,
collector_fee_details: RwLock::new(CollectorFeeDetails::default()),
compute_budget: runtime_config.compute_budget,
transaction_account_lock_limit: runtime_config.transaction_account_lock_limit,
fee_structure: FeeStructure::default(),
#[cfg(feature = "dev-context-only-utils")]
hash_overrides: Arc::new(Mutex::new(HashOverrides::default())),
accounts_lt_hash: Mutex::new(fields.accounts_lt_hash),
cache_for_accounts_lt_hash: DashMap::default(),
stats_for_accounts_lt_hash: AccountsLtHashStats::default(),
block_id: RwLock::new(None),
bank_hash_stats: AtomicBankHashStats::new(&fields.bank_hash_stats),
epoch_rewards_calculation_cache: Arc::new(Mutex::new(HashMap::default())),
expected_bank_hash: RwLock::new(None),
};
assert_eq!(
bank.genesis_creation_time, genesis_config.creation_time,
"Bank snapshot genesis creation time does not match genesis.bin creation time. The \
snapshot and genesis.bin might pertain to different clusters"
);
assert_eq!(bank.ticks_per_slot, genesis_config.ticks_per_slot);
assert_eq!(
bank.ns_per_slot,
genesis_config.poh_config.target_tick_duration.as_nanos()
* genesis_config.ticks_per_slot as u128
);
assert_eq!(bank.max_tick_height, (bank.slot + 1) * bank.ticks_per_slot);
assert_eq!(
bank.slots_per_year,
years_as_slots(
1.0,
&genesis_config.poh_config.target_tick_duration,
bank.ticks_per_slot,
)
);
assert_eq!(bank.epoch_schedule, genesis_config.epoch_schedule);
bank.initialize_after_snapshot_restore(|| {
ThreadPoolBuilder::new()
.thread_name(|i| format!("solBnkClcRwds{i:02}"))
.build()
.expect("new rayon threadpool")
});
datapoint_info!(
"bank-new-from-fields",
(
"accounts_data_len-from-snapshot",
fields.accounts_data_len as i64,
i64
),
(
"accounts_data_len-from-generate_index",
accounts_data_size_initial as i64,
i64
),
(
"stakes_accounts_load_duration_us",
stakes_accounts_load_duration.as_micros(),
i64
),
);
bank
}
pub(crate) fn get_fields_to_serialize(&self) -> BankFieldsToSerialize {
BankFieldsToSerialize {
blockhash_queue: self.blockhash_queue.read().unwrap().clone(),
hash: *self.hash.read().unwrap(),
parent_hash: self.parent_hash,
parent_slot: self.parent_slot,
hard_forks: self.hard_forks.read().unwrap().clone(),
transaction_count: self.transaction_count.load(Relaxed),
tick_height: self.tick_height.load(Relaxed),
signature_count: self.signature_count.load(Relaxed),
capitalization: self.capitalization.load(Relaxed),
max_tick_height: self.max_tick_height,
hashes_per_tick: self.hashes_per_tick,
ticks_per_slot: self.ticks_per_slot,
ns_per_slot: self.ns_per_slot,
genesis_creation_time: self.genesis_creation_time,
slots_per_year: self.slots_per_year,
slot: self.slot,
epoch: self.epoch,
block_height: self.block_height,
leader_id: self.leader_id,
fee_rate_governor: self.fee_rate_governor.clone(),
rent_collector: self.rent_collector.clone(),
epoch_schedule: self.epoch_schedule.clone(),
inflation: *self.inflation.read().unwrap(),
stakes: self.stakes_cache.stakes().clone(),
is_delta: self.is_delta.load(Relaxed),
accounts_data_len: self.load_accounts_data_size(),
versioned_epoch_stakes: self.epoch_stakes.clone(),
accounts_lt_hash: self.accounts_lt_hash.lock().unwrap().clone(),
}
}
pub fn leader_id(&self) -> &Pubkey {
&self.leader_id
}
pub fn genesis_creation_time(&self) -> UnixTimestamp {
self.genesis_creation_time
}
pub fn slot(&self) -> Slot {
self.slot
}
pub fn bank_id(&self) -> BankId {
self.bank_id
}
pub fn epoch(&self) -> Epoch {
self.epoch
}
pub fn first_normal_epoch(&self) -> Epoch {
self.epoch_schedule().first_normal_epoch
}
pub fn freeze_lock(&self) -> RwLockReadGuard<'_, Hash> {
self.hash.read().unwrap()
}
pub fn hash(&self) -> Hash {
*self.hash.read().unwrap()
}
pub fn is_frozen(&self) -> bool {
*self.hash.read().unwrap() != Hash::default()
}
pub fn freeze_started(&self) -> bool {
self.freeze_started.load(Relaxed)
}
pub fn status_cache_ancestors(&self) -> Vec<u64> {
let mut roots = self.status_cache.read().unwrap().roots().clone();
let min = roots.iter().min().cloned().unwrap_or(0);
for ancestor in self.ancestors.keys() {
if ancestor >= min {
roots.insert(ancestor);
}
}
let mut ancestors: Vec<_> = roots.into_iter().collect();
#[allow(clippy::stable_sort_primitive)]
ancestors.sort();
ancestors
}
pub fn unix_timestamp_from_genesis(&self) -> i64 {
self.genesis_creation_time.saturating_add(
(self.slot as u128)
.saturating_mul(self.ns_per_slot)
.saturating_div(1_000_000_000) as i64,
)
}
pub fn epoch_stakes_from_slot(&self, slot: Slot) -> Option<&VersionedEpochStakes> {
let epoch = self.epoch_schedule().get_epoch(slot);
self.epoch_stakes(epoch)
}
pub fn get_rank_map(&self, slot: Slot) -> Option<&Arc<BLSPubkeyToRankMap>> {
self.epoch_stakes_from_slot(slot)
.map(|stake| stake.bls_pubkey_to_rank_map())
}
fn update_sysvar_account<F>(&self, pubkey: &Pubkey, updater: F)
where
F: Fn(&Option<AccountSharedData>) -> AccountSharedData,
{
let old_account = self.get_account_with_fixed_root(pubkey);
let mut new_account = updater(&old_account);
self.adjust_sysvar_balance_for_rent(&mut new_account);
self.store_account_and_update_capitalization(pubkey, &new_account);
}
fn inherit_specially_retained_account_fields(
&self,
old_account: &Option<AccountSharedData>,
) -> InheritableAccountFields {
const RENT_UNADJUSTED_INITIAL_BALANCE: u64 = 1;
(
old_account
.as_ref()
.map(|a| a.lamports())
.unwrap_or(RENT_UNADJUSTED_INITIAL_BALANCE),
old_account
.as_ref()
.map(|a| a.rent_epoch())
.unwrap_or(INITIAL_RENT_EPOCH),
)
}
pub fn clock(&self) -> sysvar::clock::Clock {
from_account(&self.get_account(&sysvar::clock::id()).unwrap_or_default())
.unwrap_or_default()
}
fn update_clock(&self, parent_epoch: Option<Epoch>) {
let mut unix_timestamp = self.clock().unix_timestamp;
let epoch_start_timestamp = {
let epoch = if let Some(epoch) = parent_epoch {
epoch
} else {
self.epoch()
};
let first_slot_in_epoch = self.epoch_schedule().get_first_slot_in_epoch(epoch);
Some((first_slot_in_epoch, self.clock().epoch_start_timestamp))
};
let max_allowable_drift = MaxAllowableDrift {
fast: MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST,
slow: MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW_V2,
};
let ancestor_timestamp = self.clock().unix_timestamp;
if let Some(timestamp_estimate) =
self.get_timestamp_estimate(max_allowable_drift, epoch_start_timestamp)
{
unix_timestamp = timestamp_estimate;
if timestamp_estimate < ancestor_timestamp {
unix_timestamp = ancestor_timestamp;
}
}
datapoint_info!(
"bank-timestamp-correction",
("slot", self.slot(), i64),
("from_genesis", self.unix_timestamp_from_genesis(), i64),
("corrected", unix_timestamp, i64),
("ancestor_timestamp", ancestor_timestamp, i64),
);
let mut epoch_start_timestamp =
if parent_epoch.is_some() && parent_epoch.unwrap() != self.epoch() {
unix_timestamp
} else {
self.clock().epoch_start_timestamp
};
if self.slot == 0 {
unix_timestamp = self.unix_timestamp_from_genesis();
epoch_start_timestamp = self.unix_timestamp_from_genesis();
}
let clock = sysvar::clock::Clock {
slot: self.slot,
epoch_start_timestamp,
epoch: self.epoch_schedule().get_epoch(self.slot),
leader_schedule_epoch: self.epoch_schedule().get_leader_schedule_epoch(self.slot),
unix_timestamp,
};
self.update_sysvar_account(&sysvar::clock::id(), |account| {
create_account(
&clock,
self.inherit_specially_retained_account_fields(account),
)
});
}
pub fn update_last_restart_slot(&self) {
let feature_flag = self
.feature_set
.is_active(&feature_set::last_restart_slot_sysvar::id());
if feature_flag {
let current_last_restart_slot = self
.get_account(&sysvar::last_restart_slot::id())
.and_then(|account| {
let lrs: Option<LastRestartSlot> = from_account(&account);
lrs
})
.map(|account| account.last_restart_slot);
let last_restart_slot = {
let slot = self.slot;
let hard_forks_r = self.hard_forks.read().unwrap();
hard_forks_r
.iter()
.rev()
.find(|(hard_fork, _)| *hard_fork <= slot)
.map(|(slot, _)| *slot)
.unwrap_or(0)
};
if current_last_restart_slot != Some(last_restart_slot) {
self.update_sysvar_account(&sysvar::last_restart_slot::id(), |account| {
create_account(
&LastRestartSlot { last_restart_slot },
self.inherit_specially_retained_account_fields(account),
)
});
}
}
}
pub fn set_sysvar_for_tests<T>(&self, sysvar: &T)
where
T: SysvarSerialize + SysvarId,
{
self.update_sysvar_account(&T::id(), |account| {
create_account(
sysvar,
self.inherit_specially_retained_account_fields(account),
)
});
self.transaction_processor.reset_sysvar_cache();
self.transaction_processor
.fill_missing_sysvar_cache_entries(self);
}
fn update_slot_history(&self) {
self.update_sysvar_account(&sysvar::slot_history::id(), |account| {
let mut slot_history = account
.as_ref()
.map(|account| from_account::<SlotHistory, _>(account).unwrap())
.unwrap_or_default();
slot_history.add(self.slot());
create_account(
&slot_history,
self.inherit_specially_retained_account_fields(account),
)
});
}
fn update_slot_hashes(&self) {
self.update_sysvar_account(&sysvar::slot_hashes::id(), |account| {
let mut slot_hashes = account
.as_ref()
.map(|account| from_account::<SlotHashes, _>(account).unwrap())
.unwrap_or_default();
slot_hashes.add(self.parent_slot, self.parent_hash);
create_account(
&slot_hashes,
self.inherit_specially_retained_account_fields(account),
)
});
}
pub fn get_slot_history(&self) -> SlotHistory {
from_account(&self.get_account(&sysvar::slot_history::id()).unwrap()).unwrap()
}
fn update_epoch_stakes(&mut self, leader_schedule_epoch: Epoch) {
if !self.epoch_stakes.contains_key(&leader_schedule_epoch) {
self.epoch_stakes.retain(|&epoch, _| {
epoch >= leader_schedule_epoch.saturating_sub(MAX_LEADER_SCHEDULE_STAKES - 1)
});
let stakes = self.get_top_epoch_stakes();
let stakes = SerdeStakesToStakeFormat::from(stakes);
let new_epoch_stakes = VersionedEpochStakes::new(stakes, leader_schedule_epoch);
info!(
"new epoch stakes, epoch: {}, total_stake: {}",
leader_schedule_epoch,
new_epoch_stakes.total_stake(),
);
self.maybe_burn_vat_from_staked_accounts(&new_epoch_stakes);
if log::log_enabled!(log::Level::Trace) {
let vote_stakes: HashMap<_, _> = self
.stakes_cache
.stakes()
.vote_accounts()
.delegated_stakes()
.map(|(pubkey, stake)| (*pubkey, stake))
.collect();
trace!("new epoch stakes, stakes: {vote_stakes:#?}");
}
self.epoch_stakes
.insert(leader_schedule_epoch, new_epoch_stakes);
}
}
fn maybe_burn_vat_from_staked_accounts(&mut self, epoch_stakes: &VersionedEpochStakes) {
if !self
.feature_set
.is_active(&agave_feature_set::alpenglow::id())
|| !self
.feature_set
.is_active(&agave_feature_set::validator_admission_ticket::id())
{
return;
}
let vote_accounts = epoch_stakes.stakes().vote_accounts();
debug_assert!(vote_accounts.len() <= 2000);
let mut accounts_to_store: Vec<(Pubkey, AccountSharedData)> =
Vec::with_capacity(vote_accounts.len() + 1);
let mut total_vat = 0u64;
for (vote_pubkey, _stake) in vote_accounts.delegated_stakes() {
let mut account = self.get_account(vote_pubkey).unwrap();
total_vat += VAT_TO_BURN_PER_EPOCH;
account.set_lamports(
account
.lamports()
.checked_sub(VAT_TO_BURN_PER_EPOCH)
.expect(
"Vote accounts should have already been filtered to contain enough \
balance for the VAT",
),
);
accounts_to_store.push((*vote_pubkey, account));
}
let mut incinerator_account = self.get_account(&incinerator::id()).unwrap_or_default();
incinerator_account.set_lamports(
incinerator_account
.lamports()
.checked_add(total_vat)
.unwrap(),
);
accounts_to_store.push((incinerator::id(), incinerator_account));
self.store_accounts((self.slot, accounts_to_store.as_slice()));
info!(
"Transferred total VAT of {total_vat} lamports to incinerator from staked vote \
accounts"
);
}
#[cfg(feature = "dev-context-only-utils")]
pub fn set_epoch_stakes_for_test(&mut self, epoch: Epoch, stakes: VersionedEpochStakes) {
self.epoch_stakes.insert(epoch, stakes);
}
fn update_rent(&self) {
self.update_sysvar_account(&sysvar::rent::id(), |account| {
create_account(
&self.rent_collector.rent,
self.inherit_specially_retained_account_fields(account),
)
});
}
fn update_epoch_schedule(&self) {
self.update_sysvar_account(&sysvar::epoch_schedule::id(), |account| {
create_account(
self.epoch_schedule(),
self.inherit_specially_retained_account_fields(account),
)
});
}
fn update_stake_history(&self, epoch: Option<Epoch>) {
if epoch == Some(self.epoch()) {
return;
}
self.update_sysvar_account(&stake_history::id(), |account| {
create_account::<StakeHistory>(
self.stakes_cache.stakes().history(),
self.inherit_specially_retained_account_fields(account),
)
});
}
pub fn epoch_duration_in_years(&self, epoch: Epoch) -> f64 {
self.epoch_schedule().get_slots_in_epoch(epoch) as f64 / self.slots_per_year
}
fn get_inflation_start_slot(&self) -> Slot {
let mut slots = self
.feature_set
.full_inflation_features_enabled()
.iter()
.filter_map(|id| self.feature_set.activated_slot(id))
.collect::<Vec<_>>();
slots.sort_unstable();
slots.first().cloned().unwrap_or_else(|| {
self.feature_set
.activated_slot(&feature_set::pico_inflation::id())
.unwrap_or(0)
})
}
fn get_inflation_num_slots(&self) -> u64 {
let inflation_activation_slot = self.get_inflation_start_slot();
let inflation_start_slot = self.epoch_schedule().get_first_slot_in_epoch(
self.epoch_schedule()
.get_epoch(inflation_activation_slot)
.saturating_sub(1),
);
self.epoch_schedule().get_first_slot_in_epoch(self.epoch()) - inflation_start_slot
}
pub fn slot_in_year_for_inflation(&self) -> f64 {
let num_slots = self.get_inflation_num_slots();
num_slots as f64 / self.slots_per_year
}
fn calculate_epoch_inflation_rewards(
&self,
capitalization: u64,
epoch: Epoch,
) -> EpochInflationRewards {
let slot_in_year = self.slot_in_year_for_inflation();
let (validator_rate, foundation_rate) = {
let inflation = self.inflation.read().unwrap();
(
(*inflation).validator(slot_in_year),
(*inflation).foundation(slot_in_year),
)
};
let epoch_duration_in_years = self.epoch_duration_in_years(epoch);
let validator_rewards_lamports =
(validator_rate * capitalization as f64 * epoch_duration_in_years) as u64;
EpochInflationRewards {
validator_rewards_lamports,
validator_rate,
foundation_rate,
}
}
fn calculate_commission_accounts(
reward_commissions: RewardCommissions,
) -> RewardCommissionAccounts {
let mut result = RewardCommissionAccounts {
accounts_with_rewards: Vec::with_capacity(reward_commissions.len()),
total_reward_commission_lamports: 0,
};
for (
commission_pubkey,
RewardCommission {
mut commission_account,
commission_bps,
commission_lamports,
},
) in reward_commissions
{
if let Err(err) = commission_account.checked_add_lamports(commission_lamports) {
debug!("reward redemption failed for {commission_pubkey}: {err:?}");
continue;
}
result.accounts_with_rewards.push((
commission_pubkey,
RewardInfo {
reward_type: RewardType::Voting,
lamports: commission_lamports as i64,
post_balance: commission_account.lamports(),
commission_bps: Some(commission_bps),
},
commission_account,
));
result.total_reward_commission_lamports += commission_lamports;
}
result
}
fn update_reward_commissions(&self, reward_commission_accounts: &RewardCommissionAccounts) {
let mut rewards = self.rewards.write().unwrap();
rewards.reserve(reward_commission_accounts.accounts_with_rewards.len());
reward_commission_accounts
.accounts_with_rewards
.iter()
.for_each(|(commission_pubkey, reward_commission, _)| {
rewards.push((*commission_pubkey, *reward_commission));
});
}
fn update_recent_blockhashes_locked(&self, locked_blockhash_queue: &BlockhashQueue) {
#[allow(deprecated)]
self.update_sysvar_account(&sysvar::recent_blockhashes::id(), |account| {
let recent_blockhash_iter = locked_blockhash_queue.get_recent_blockhashes();
recent_blockhashes_account::create_account_with_data_and_fields(
recent_blockhash_iter,
self.inherit_specially_retained_account_fields(account),
)
});
}
pub fn update_recent_blockhashes(&self) {
let blockhash_queue = self.blockhash_queue.read().unwrap();
self.update_recent_blockhashes_locked(&blockhash_queue);
}
fn get_timestamp_estimate(
&self,
max_allowable_drift: MaxAllowableDrift,
epoch_start_timestamp: Option<(Slot, UnixTimestamp)>,
) -> Option<UnixTimestamp> {
let mut get_timestamp_estimate_time = Measure::start("get_timestamp_estimate");
let slots_per_epoch = self.epoch_schedule().slots_per_epoch;
let vote_accounts = self.vote_accounts();
let recent_timestamps = vote_accounts.iter().filter_map(|(pubkey, (_, account))| {
let vote_state = account.vote_state_view();
let last_timestamp = vote_state.last_timestamp();
let slot_delta = self.slot().checked_sub(last_timestamp.slot)?;
(slot_delta <= slots_per_epoch)
.then_some((*pubkey, (last_timestamp.slot, last_timestamp.timestamp)))
});
let slot_duration = Duration::from_nanos(self.ns_per_slot as u64);
let epoch = self.epoch_schedule().get_epoch(self.slot());
let stakes = self.epoch_vote_accounts(epoch)?;
let stake_weighted_timestamp = calculate_stake_weighted_timestamp(
recent_timestamps,
stakes,
self.slot(),
slot_duration,
epoch_start_timestamp,
max_allowable_drift,
self.feature_set
.is_active(&feature_set::warp_timestamp_again::id()),
);
get_timestamp_estimate_time.stop();
datapoint_info!(
"bank-timestamp",
(
"get_timestamp_estimate_us",
get_timestamp_estimate_time.as_us(),
i64
),
);
stake_weighted_timestamp
}
pub fn rehash(&self) {
let mut hash = self.hash.write().unwrap();
let new = self.hash_internal_state();
if new != *hash {
warn!("Updating bank hash to {new}");
*hash = new;
}
}
pub fn freeze(&self) {
let mut hash = self.hash.write().unwrap();
if *hash == Hash::default() {
self.distribute_transaction_fee_details();
self.update_slot_history();
self.run_incinerator();
self.freeze_started.store(true, Relaxed);
self.update_accounts_lt_hash();
*hash = self.hash_internal_state();
self.rc.accounts.accounts_db.mark_slot_frozen(self.slot());
}
}
pub fn freeze_and_verify_bank_hash(&self) -> std::result::Result<(), (Hash, Hash)> {
self.freeze();
let computed_hash = self.hash();
if let Some(expected_hash) = self.expected_bank_hash() {
if expected_hash != computed_hash {
return Err((expected_hash, computed_hash));
}
}
Ok(())
}
pub fn set_expected_bank_hash(&self, hash: Hash) {
*self.expected_bank_hash.write().unwrap() = Some(hash);
}
pub fn expected_bank_hash(&self) -> Option<Hash> {
*self.expected_bank_hash.read().unwrap()
}
#[cfg(feature = "dev-context-only-utils")]
pub fn unfreeze_for_ledger_tool(&self) {
self.freeze_started.store(false, Relaxed);
}
pub fn epoch_schedule(&self) -> &EpochSchedule {
&self.epoch_schedule
}
pub fn squash(&self) -> SquashTiming {
self.freeze();
let mut roots = vec![self.slot()];
roots.append(&mut self.parents().iter().map(|p| p.slot()).collect());
let mut total_index_us = 0;
let mut total_cache_us = 0;
let mut squash_accounts_time = Measure::start("squash_accounts_time");
for slot in roots.iter().rev() {
let add_root_timing = self.rc.accounts.add_root(*slot);
total_index_us += add_root_timing.index_us;
total_cache_us += add_root_timing.cache_us;
}
squash_accounts_time.stop();
*self.rc.parent.write().unwrap() = None;
let mut squash_cache_time = Measure::start("squash_cache_time");
roots
.iter()
.for_each(|slot| self.status_cache.write().unwrap().add_root(*slot));
squash_cache_time.stop();
SquashTiming {
squash_accounts_ms: squash_accounts_time.as_ms(),
squash_accounts_index_ms: total_index_us / 1000,
squash_accounts_cache_ms: total_cache_us / 1000,
squash_cache_ms: squash_cache_time.as_ms(),
}
}
pub fn parent(&self) -> Option<Arc<Bank>> {
self.rc.parent.read().unwrap().clone()
}
pub fn parent_slot(&self) -> Slot {
self.parent_slot
}
pub fn parent_hash(&self) -> Hash {
self.parent_hash
}
fn process_genesis_config(
&mut self,
genesis_config: &GenesisConfig,
#[cfg(feature = "dev-context-only-utils")] leader_id_for_tests: Option<Pubkey>,
#[cfg(feature = "dev-context-only-utils")] genesis_hash: Option<Hash>,
) {
self.fee_rate_governor = genesis_config.fee_rate_governor.clone();
for (pubkey, account) in genesis_config.accounts.iter() {
assert!(
self.get_account(pubkey).is_none(),
"{pubkey} repeated in genesis config"
);
let account_shared_data = create_account_shared_data(account);
self.store_account(pubkey, &account_shared_data);
self.capitalization.fetch_add(account.lamports(), Relaxed);
self.accounts_data_size_initial += account.data().len() as u64;
}
for (pubkey, account) in genesis_config.rewards_pools.iter() {
assert!(
self.get_account(pubkey).is_none(),
"{pubkey} repeated in genesis config"
);
let account_shared_data = create_account_shared_data(account);
self.store_account(pubkey, &account_shared_data);
self.accounts_data_size_initial += account.data().len() as u64;
}
let leader_id = self.stakes_cache.stakes().highest_staked_node().copied();
#[cfg(feature = "dev-context-only-utils")]
let leader_id = leader_id.or(leader_id_for_tests);
self.leader_id =
leader_id.expect("genesis processing failed because no staked nodes exist");
#[cfg(not(feature = "dev-context-only-utils"))]
let genesis_hash = genesis_config.hash();
#[cfg(feature = "dev-context-only-utils")]
let genesis_hash = genesis_hash.unwrap_or(genesis_config.hash());
self.blockhash_queue.write().unwrap().genesis_hash(
&genesis_hash,
genesis_config.fee_rate_governor.lamports_per_signature,
);
self.hashes_per_tick = genesis_config.hashes_per_tick();
self.ticks_per_slot = genesis_config.ticks_per_slot();
self.ns_per_slot = genesis_config.ns_per_slot();
self.genesis_creation_time = genesis_config.creation_time;
self.max_tick_height = (self.slot + 1) * self.ticks_per_slot;
self.slots_per_year = genesis_config.slots_per_year();
self.epoch_schedule = genesis_config.epoch_schedule.clone();
self.inflation = Arc::new(RwLock::new(genesis_config.inflation));
self.rent_collector = RentCollector::new(
self.epoch,
self.epoch_schedule().clone(),
self.slots_per_year,
genesis_config.rent.clone(),
);
}
fn burn_and_purge_account(&self, program_id: &Pubkey, mut account: AccountSharedData) {
let old_data_size = account.data().len();
self.capitalization.fetch_sub(account.lamports(), Relaxed);
account.set_lamports(0);
account.data_as_mut_slice().fill(0);
self.store_account(program_id, &account);
self.calculate_and_update_accounts_data_size_delta_off_chain(old_data_size, 0);
}
pub fn add_precompiled_account(&self, program_id: &Pubkey) {
self.add_precompiled_account_with_owner(program_id, native_loader::id())
}
fn add_precompiled_account_with_owner(&self, program_id: &Pubkey, owner: Pubkey) {
if let Some(account) = self.get_account_with_fixed_root(program_id) {
if account.executable() {
return;
} else {
self.burn_and_purge_account(program_id, account);
}
};
assert!(
!self.freeze_started(),
"Can't change frozen bank by adding not-existing new precompiled program \
({program_id}). Maybe, inconsistent program activation is detected on snapshot \
restore?"
);
let (lamports, rent_epoch) = self.inherit_specially_retained_account_fields(&None);
let account = AccountSharedData::from(Account {
lamports,
owner,
data: vec![],
executable: true,
rent_epoch,
});
self.store_account_and_update_capitalization(program_id, &account);
}
pub fn set_rent_burn_percentage(&mut self, burn_percent: u8) {
self.rent_collector.rent.burn_percent = burn_percent;
}
pub fn set_hashes_per_tick(&mut self, hashes_per_tick: Option<u64>) {
self.hashes_per_tick = hashes_per_tick;
}
pub fn last_blockhash(&self) -> Hash {
self.blockhash_queue.read().unwrap().last_hash()
}
pub fn last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64) {
let blockhash_queue = self.blockhash_queue.read().unwrap();
let last_hash = blockhash_queue.last_hash();
let last_lamports_per_signature = blockhash_queue
.get_lamports_per_signature(&last_hash)
.unwrap(); (last_hash, last_lamports_per_signature)
}
pub fn is_blockhash_valid(&self, hash: &Hash) -> bool {
let blockhash_queue = self.blockhash_queue.read().unwrap();
blockhash_queue.is_hash_valid_for_age(hash, MAX_PROCESSING_AGE)
}
pub fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> u64 {
self.rent_collector.rent.minimum_balance(data_len).max(1)
}
pub fn get_lamports_per_signature(&self) -> u64 {
self.fee_rate_governor.lamports_per_signature
}
pub fn get_lamports_per_signature_for_blockhash(&self, hash: &Hash) -> Option<u64> {
let blockhash_queue = self.blockhash_queue.read().unwrap();
blockhash_queue.get_lamports_per_signature(hash)
}
pub fn get_fee_for_message(&self, message: &SanitizedMessage) -> Option<u64> {
let lamports_per_signature = {
let blockhash_queue = self.blockhash_queue.read().unwrap();
blockhash_queue.get_lamports_per_signature(message.recent_blockhash())
}
.or_else(|| {
self.load_message_nonce_data(message)
.map(|(_nonce_address, nonce_data)| nonce_data.get_lamports_per_signature())
})?;
Some(self.get_fee_for_message_with_lamports_per_signature(message, lamports_per_signature))
}
pub fn get_fee_for_message_with_lamports_per_signature(
&self,
message: &impl SVMMessage,
lamports_per_signature: u64,
) -> u64 {
let fee_budget_limits = FeeBudgetLimits::from(
process_compute_budget_instructions(
message.program_instructions_iter(),
&self.feature_set,
)
.unwrap_or_default(),
);
solana_fee::calculate_fee(
message,
lamports_per_signature == 0,
self.fee_structure().lamports_per_signature,
fee_budget_limits.prioritization_fee,
FeeFeatures::from(self.feature_set.as_ref()),
)
}
pub fn get_blockhash_last_valid_block_height(&self, blockhash: &Hash) -> Option<Slot> {
let blockhash_queue = self.blockhash_queue.read().unwrap();
blockhash_queue
.get_hash_age(blockhash)
.map(|age| self.block_height + MAX_PROCESSING_AGE as u64 - age)
}
pub fn get_alpenglow_genesis_certificate(&self) -> Option<Certificate> {
let acct = self.get_account(&GENESIS_CERTIFICATE_ACCOUNT)?;
(!acct.data().is_empty()).then(|| {
wincode::deserialize(acct.data())
.expect("Programmer error deserializing genesis certificate")
})
}
pub fn set_alpenglow_genesis_certificate(&self, cert: &Certificate) {
let data = wincode::serialize(cert).unwrap();
let lamports = Rent::default().minimum_balance(data.len());
let mut cert_acct = AccountSharedData::new(lamports, data.len(), &system_program::ID);
cert_acct.set_data_from_slice(&data);
self.store_account_and_update_capitalization(&GENESIS_CERTIFICATE_ACCOUNT, &cert_acct);
}
pub fn update_clock_from_footer(&self, unix_timestamp_nanos: i64) {
if !self.feature_set.is_active(&feature_set::alpenglow::id()) {
return;
}
let unix_timestamp_s = unix_timestamp_nanos / 1_000_000_000;
let epoch_start_timestamp = match (self.slot, self.parent()) {
(0, _) => self.unix_timestamp_from_genesis(),
(_, Some(parent)) if parent.epoch() != self.epoch() => unix_timestamp_s,
_ => self.clock().epoch_start_timestamp,
};
let clock = sysvar::clock::Clock {
slot: self.slot,
epoch_start_timestamp,
epoch: self.epoch_schedule().get_epoch(self.slot),
leader_schedule_epoch: self.epoch_schedule().get_leader_schedule_epoch(self.slot),
unix_timestamp: unix_timestamp_s,
};
self.update_sysvar_account(&sysvar::clock::id(), |account| {
create_account(
&clock,
self.inherit_specially_retained_account_fields(account),
)
});
let data = wincode::serialize(&unix_timestamp_nanos).unwrap();
let lamports = Rent::default().minimum_balance(data.len());
let mut alpenclock_acct = AccountSharedData::new(lamports, data.len(), &system_program::ID);
alpenclock_acct.set_data_from_slice(&data);
self.store_account_and_update_capitalization(&NANOSECOND_CLOCK_ACCOUNT, &alpenclock_acct);
}
pub fn get_nanosecond_clock(&self) -> Option<i64> {
self.get_account(&NANOSECOND_CLOCK_ACCOUNT).map(|acct| {
wincode::deserialize(acct.data())
.expect("Couldn't deserialize nanosecond resolution clock")
})
}
pub fn confirmed_last_blockhash(&self) -> Hash {
const NUM_BLOCKHASH_CONFIRMATIONS: usize = 3;
let parents = self.parents();
if parents.is_empty() {
self.last_blockhash()
} else {
let index = NUM_BLOCKHASH_CONFIRMATIONS.min(parents.len() - 1);
parents[index].last_blockhash()
}
}
#[cfg(feature = "dev-context-only-utils")]
pub fn clear_signatures(&self) {
self.status_cache.write().unwrap().clear();
}
pub fn clear_slot_signatures(&self, slot: Slot) {
self.status_cache.write().unwrap().clear_slot_entries(slot);
}
fn update_transaction_statuses(
&self,
sanitized_txs: &[impl TransactionWithMeta],
processing_results: &[TransactionProcessingResult],
) {
let mut status_cache = self.status_cache.write().unwrap();
assert_eq!(sanitized_txs.len(), processing_results.len());
for (tx, processing_result) in sanitized_txs.iter().zip(processing_results) {
if let Ok(processed_tx) = &processing_result {
status_cache.insert(
tx.recent_blockhash(),
tx.message_hash(),
self.slot(),
processed_tx.status(),
);
status_cache.insert(
tx.recent_blockhash(),
tx.signature(),
self.slot(),
processed_tx.status(),
);
}
}
}
fn register_recent_blockhash(&self, blockhash: &Hash, scheduler: &InstalledSchedulerRwLock) {
BankWithScheduler::wait_for_paused_scheduler(self, scheduler);
let mut w_blockhash_queue = self.blockhash_queue.write().unwrap();
#[cfg(feature = "dev-context-only-utils")]
let blockhash_override = self
.hash_overrides
.lock()
.unwrap()
.get_blockhash_override(self.slot())
.copied()
.inspect(|blockhash_override| {
if blockhash_override != blockhash {
info!(
"bank: slot: {}: overrode blockhash: {} with {}",
self.slot(),
blockhash,
blockhash_override
);
}
});
#[cfg(feature = "dev-context-only-utils")]
let blockhash = blockhash_override.as_ref().unwrap_or(blockhash);
w_blockhash_queue.register_hash(blockhash, self.fee_rate_governor.lamports_per_signature);
self.update_recent_blockhashes_locked(&w_blockhash_queue);
}
pub fn register_unique_recent_blockhash_for_test(&self) {
self.register_recent_blockhash(
&Hash::new_unique(),
&BankWithScheduler::no_scheduler_available(),
)
}
#[cfg(feature = "dev-context-only-utils")]
pub fn register_recent_blockhash_for_test(
&self,
blockhash: &Hash,
lamports_per_signature: Option<u64>,
) {
let mut w_blockhash_queue = self.blockhash_queue.write().unwrap();
if let Some(lamports_per_signature) = lamports_per_signature {
w_blockhash_queue.register_hash(blockhash, lamports_per_signature);
} else {
w_blockhash_queue
.register_hash(blockhash, self.fee_rate_governor.lamports_per_signature);
}
}
pub fn register_tick(&self, hash: &Hash, scheduler: &InstalledSchedulerRwLock) {
assert!(
!self.freeze_started(),
"register_tick() working on a bank that is already frozen or is undergoing freezing!"
);
if self.is_block_boundary(self.tick_height.load(Relaxed) + 1) {
self.register_recent_blockhash(hash, scheduler);
}
self.tick_height.fetch_add(1, Relaxed);
}
#[cfg(feature = "dev-context-only-utils")]
pub fn register_tick_for_test(&self, hash: &Hash) {
self.register_tick(hash, &BankWithScheduler::no_scheduler_available())
}
#[cfg(feature = "dev-context-only-utils")]
pub fn register_default_tick_for_test(&self) {
self.register_tick_for_test(&Hash::default())
}
pub fn is_complete(&self) -> bool {
self.tick_height() == self.max_tick_height()
}
pub fn is_block_boundary(&self, tick_height: u64) -> bool {
tick_height == self.max_tick_height
}
pub fn get_transaction_account_lock_limit(&self) -> usize {
if let Some(transaction_account_lock_limit) = self.transaction_account_lock_limit {
transaction_account_lock_limit
} else if self
.feature_set
.is_active(&feature_set::increase_tx_account_lock_limit::id())
{
MAX_TX_ACCOUNT_LOCKS
} else {
64
}
}
pub fn prepare_entry_batch(
&self,
txs: Vec<VersionedTransaction>,
) -> Result<TransactionBatch<'_, '_, RuntimeTransaction<SanitizedTransaction>>> {
let enable_static_instruction_limit = self
.feature_set
.is_active(&agave_feature_set::static_instruction_limit::id());
let enable_instruction_account_limit = self
.feature_set
.is_active(&agave_feature_set::limit_instruction_accounts::id());
let sanitized_txs = txs
.into_iter()
.map(|tx| {
RuntimeTransaction::try_create(
tx,
MessageHash::Compute,
None,
self,
self.get_reserved_account_keys(),
enable_static_instruction_limit,
enable_instruction_account_limit,
)
})
.collect::<Result<Vec<_>>>()?;
Ok(TransactionBatch::new(
self.try_lock_accounts(&sanitized_txs),
self,
OwnedOrBorrowed::Owned(sanitized_txs),
))
}
pub fn try_lock_accounts(&self, txs: &[impl TransactionWithMeta]) -> Vec<Result<()>> {
self.try_lock_accounts_with_results(txs, txs.iter().map(|_| Ok(())))
}
pub fn try_lock_accounts_with_results(
&self,
txs: &[impl TransactionWithMeta],
tx_results: impl Iterator<Item = Result<()>>,
) -> Vec<Result<()>> {
let tx_account_lock_limit = self.get_transaction_account_lock_limit();
let relax_intrabatch_account_locks = self
.feature_set
.is_active(&feature_set::relax_intrabatch_account_locks::id());
let mut batch_message_hashes = AHashSet::with_capacity(txs.len());
let tx_results = tx_results
.enumerate()
.map(|(i, tx_result)| match tx_result {
Ok(()) if relax_intrabatch_account_locks => {
if batch_message_hashes.insert(txs[i].message_hash()) {
Ok(())
} else {
Err(TransactionError::AlreadyProcessed)
}
}
Ok(()) => Ok(()),
Err(e) => Err(e),
});
self.rc.accounts.lock_accounts(
txs.iter(),
tx_results,
tx_account_lock_limit,
relax_intrabatch_account_locks,
)
}
pub fn prepare_sanitized_batch<'a, 'b, Tx: TransactionWithMeta>(
&'a self,
txs: &'b [Tx],
) -> TransactionBatch<'a, 'b, Tx> {
self.prepare_sanitized_batch_with_results(txs, txs.iter().map(|_| Ok(())))
}
pub fn prepare_sanitized_batch_with_results<'a, 'b, Tx: TransactionWithMeta>(
&'a self,
transactions: &'b [Tx],
transaction_results: impl Iterator<Item = Result<()>>,
) -> TransactionBatch<'a, 'b, Tx> {
TransactionBatch::new(
self.try_lock_accounts_with_results(transactions, transaction_results),
self,
OwnedOrBorrowed::Borrowed(transactions),
)
}
pub fn prepare_unlocked_batch_from_single_tx<'a, Tx: SVMMessage>(
&'a self,
transaction: &'a Tx,
) -> TransactionBatch<'a, 'a, Tx> {
let tx_account_lock_limit = self.get_transaction_account_lock_limit();
let lock_result = validate_account_locks(transaction.account_keys(), tx_account_lock_limit);
let mut batch = TransactionBatch::new(
vec![lock_result],
self,
OwnedOrBorrowed::Borrowed(slice::from_ref(transaction)),
);
batch.set_needs_unlock(false);
batch
}
pub fn prepare_locked_batch_from_single_tx<'a, Tx: TransactionWithMeta>(
&'a self,
transaction: &'a Tx,
) -> TransactionBatch<'a, 'a, Tx> {
self.prepare_sanitized_batch(slice::from_ref(transaction))
}
pub fn resanitize_transaction_minimally(
&self,
transaction: &impl TransactionWithMeta,
sanitized_epoch: Epoch,
alt_invalidation_slot: Slot,
) -> Result<()> {
if self.vote_only_bank() && !vote_parser::is_valid_vote_only_transaction(transaction) {
return Err(TransactionError::SanitizeFailure);
}
if self.epoch() != sanitized_epoch {
self.check_reserved_keys(transaction)?;
if self
.feature_set
.is_active(&agave_feature_set::limit_instruction_accounts::ID)
{
for instr in transaction.instructions_iter() {
if instr.accounts.len()
> solana_transaction_context::MAX_ACCOUNTS_PER_INSTRUCTION
{
return Err(solana_transaction_error::TransactionError::SanitizeFailure);
}
}
}
}
if self.slot() > alt_invalidation_slot {
let (_addresses, _deactivation_slot) =
self.load_addresses_from_ref(transaction.message_address_table_lookups())?;
}
Ok(())
}
pub fn simulate_transaction(
&self,
transaction: &impl TransactionWithMeta,
enable_cpi_recording: bool,
) -> TransactionSimulationResult {
assert!(self.is_frozen(), "simulation bank must be frozen");
self.simulate_transaction_unchecked(transaction, enable_cpi_recording)
}
pub fn simulate_transaction_unchecked(
&self,
transaction: &impl TransactionWithMeta,
enable_cpi_recording: bool,
) -> TransactionSimulationResult {
let account_keys = transaction.account_keys();
let number_of_accounts = account_keys.len();
let account_overrides = self.get_account_overrides_for_simulation(&account_keys);
let batch = self.prepare_unlocked_batch_from_single_tx(transaction);
let mut timings = ExecuteTimings::default();
let LoadAndExecuteTransactionsOutput {
mut processing_results,
balance_collector,
..
} = self.load_and_execute_transactions(
&batch,
MAX_PROCESSING_AGE - MAX_TRANSACTION_FORWARDING_DELAY,
&mut timings,
&mut TransactionErrorMetrics::default(),
TransactionProcessingConfig {
account_overrides: Some(&account_overrides),
check_program_deployment_slot: self.check_program_deployment_slot,
log_messages_bytes_limit: None,
limit_to_load_programs: true,
recording_config: ExecutionRecordingConfig {
enable_cpi_recording,
enable_log_recording: true,
enable_return_data_recording: true,
enable_transaction_balance_recording: true,
},
drop_on_failure: false,
all_or_nothing: false,
},
);
debug!("simulate_transaction: {timings:?}");
let processing_result = processing_results
.pop()
.unwrap_or(Err(TransactionError::InvalidProgramForExecution));
let (
post_simulation_accounts,
result,
fee,
logs,
return_data,
inner_instructions,
units_consumed,
loaded_accounts_data_size,
) = match processing_result {
Ok(processed_tx) => {
let executed_units = processed_tx.executed_units();
let loaded_accounts_data_size = processed_tx.loaded_accounts_data_size();
match processed_tx {
ProcessedTransaction::Executed(executed_tx) => {
let details = executed_tx.execution_details;
let post_simulation_accounts = executed_tx
.loaded_transaction
.accounts
.into_iter()
.take(number_of_accounts)
.collect::<Vec<_>>();
(
post_simulation_accounts,
details.status,
Some(executed_tx.loaded_transaction.fee_details.total_fee()),
details.log_messages,
details.return_data,
details.inner_instructions,
executed_units,
loaded_accounts_data_size,
)
}
ProcessedTransaction::FeesOnly(fees_only_tx) => (
vec![],
Err(fees_only_tx.load_error),
Some(fees_only_tx.fee_details.total_fee()),
None,
None,
None,
executed_units,
loaded_accounts_data_size,
),
}
}
Err(error) => (vec![], Err(error), None, None, None, None, 0, 0),
};
let logs = logs.unwrap_or_default();
let (pre_balances, post_balances, pre_token_balances, post_token_balances) =
match balance_collector {
Some(balance_collector) => {
let (mut native_pre, mut native_post, mut token_pre, mut token_post) =
balance_collector.into_vecs();
(
native_pre.pop(),
native_post.pop(),
token_pre.pop(),
token_post.pop(),
)
}
None => (None, None, None, None),
};
TransactionSimulationResult {
result,
logs,
post_simulation_accounts,
units_consumed,
loaded_accounts_data_size,
return_data,
inner_instructions,
fee,
pre_balances,
post_balances,
pre_token_balances,
post_token_balances,
}
}
fn get_account_overrides_for_simulation(&self, account_keys: &AccountKeys) -> AccountOverrides {
let mut account_overrides = AccountOverrides::default();
let slot_history_id = sysvar::slot_history::id();
if account_keys.iter().any(|pubkey| *pubkey == slot_history_id) {
let current_account = self.get_account_with_fixed_root(&slot_history_id);
let slot_history = current_account
.as_ref()
.map(|account| from_account::<SlotHistory, _>(account).unwrap())
.unwrap_or_default();
if slot_history.check(self.slot()) == Check::Found {
let ancestors = Ancestors::from(self.proper_ancestors().collect::<Vec<_>>());
if let Some((account, _)) =
self.load_slow_with_fixed_root(&ancestors, &slot_history_id)
{
account_overrides.set_slot_history(Some(account));
}
}
}
account_overrides
}
pub fn unlock_accounts<'a, Tx: SVMMessage + 'a>(
&self,
txs_and_results: impl Iterator<Item = (&'a Tx, &'a Result<()>)> + Clone,
) {
self.rc.accounts.unlock_accounts(txs_and_results)
}
pub fn remove_unrooted_slots(&self, slots: &[(Slot, BankId)]) {
self.rc.accounts.accounts_db.remove_unrooted_slots(slots)
}
pub fn get_hash_age(&self, hash: &Hash) -> Option<u64> {
self.blockhash_queue.read().unwrap().get_hash_age(hash)
}
pub fn is_hash_valid_for_age(&self, hash: &Hash, max_age: usize) -> bool {
self.blockhash_queue
.read()
.unwrap()
.is_hash_valid_for_age(hash, max_age)
}
pub fn collect_balances(
&self,
batch: &TransactionBatch<impl SVMMessage>,
) -> TransactionBalances {
let mut balances: TransactionBalances = vec![];
for transaction in batch.sanitized_transactions() {
let mut transaction_balances: Vec<u64> = vec![];
for account_key in transaction.account_keys().iter() {
transaction_balances.push(self.get_balance(account_key));
}
balances.push(transaction_balances);
}
balances
}
pub fn load_and_execute_transactions(
&self,
batch: &TransactionBatch<impl TransactionWithMeta>,
max_age: usize,
timings: &mut ExecuteTimings,
error_counters: &mut TransactionErrorMetrics,
processing_config: TransactionProcessingConfig,
) -> LoadAndExecuteTransactionsOutput {
let sanitized_txs = batch.sanitized_transactions();
let (check_results, check_us) = measure_us!(self.check_transactions(
sanitized_txs,
batch.lock_results(),
max_age,
error_counters,
));
timings.saturating_add_in_place(ExecuteTimingType::CheckUs, check_us);
let (blockhash, blockhash_lamports_per_signature) =
self.last_blockhash_and_lamports_per_signature();
let effective_epoch_of_deployments =
self.epoch_schedule().get_epoch(self.slot.saturating_add(
solana_program_runtime::loaded_programs::DELAY_VISIBILITY_SLOT_OFFSET,
));
let processing_environment = TransactionProcessingEnvironment {
blockhash,
blockhash_lamports_per_signature,
epoch_total_stake: self.get_current_epoch_total_stake(),
feature_set: self.feature_set.runtime_features(),
program_runtime_environments_for_execution: self
.transaction_processor
.environments
.clone(),
program_runtime_environments_for_deployment: self
.transaction_processor
.get_environments_for_epoch(effective_epoch_of_deployments),
rent: self.rent_collector.rent.clone(),
};
let sanitized_output = self
.transaction_processor
.load_and_execute_sanitized_transactions(
self,
sanitized_txs,
check_results,
&processing_environment,
&processing_config,
);
error_counters.accumulate(&sanitized_output.error_metrics);
timings.accumulate(&sanitized_output.execute_timings);
let ((), collect_logs_us) =
measure_us!(self.collect_logs(sanitized_txs, &sanitized_output.processing_results));
timings.saturating_add_in_place(ExecuteTimingType::CollectLogsUs, collect_logs_us);
let mut processed_counts = ProcessedTransactionCounts::default();
let err_count = &mut error_counters.total;
for (processing_result, tx) in sanitized_output
.processing_results
.iter()
.zip(sanitized_txs)
{
if let Some(debug_keys) = &self.transaction_debug_keys {
for key in tx.account_keys().iter() {
if debug_keys.contains(key) {
let result = processing_result.flattened_result();
info!("slot: {} result: {:?} tx: {:?}", self.slot, result, tx);
break;
}
}
}
if processing_result.was_processed() {
processed_counts.signature_count +=
tx.signature_details().num_transaction_signatures();
processed_counts.processed_transactions_count += 1;
if !tx.is_simple_vote_transaction() {
processed_counts.processed_non_vote_transactions_count += 1;
}
}
match processing_result.flattened_result() {
Ok(()) => {
processed_counts.processed_with_successful_result_count += 1;
}
Err(err) => {
if err_count.0 == 0 {
debug!("tx error: {err:?} {tx:?}");
}
*err_count += 1;
}
}
}
LoadAndExecuteTransactionsOutput {
processing_results: sanitized_output.processing_results,
processed_counts,
balance_collector: sanitized_output.balance_collector,
}
}
fn collect_logs(
&self,
transactions: &[impl TransactionWithMeta],
processing_results: &[TransactionProcessingResult],
) {
let transaction_log_collector_config =
self.transaction_log_collector_config.read().unwrap();
if transaction_log_collector_config.filter == TransactionLogCollectorFilter::None {
return;
}
let collected_logs: Vec<_> = processing_results
.iter()
.zip(transactions)
.filter_map(|(processing_result, transaction)| {
let processed_tx = processing_result.processed_transaction()?;
let execution_details = processed_tx.execution_details()?;
Self::collect_transaction_logs(
&transaction_log_collector_config,
transaction,
execution_details,
)
})
.collect();
if !collected_logs.is_empty() {
let mut transaction_log_collector = self.transaction_log_collector.write().unwrap();
for (log, filtered_mentioned_addresses) in collected_logs {
let transaction_log_index = transaction_log_collector.logs.len();
transaction_log_collector.logs.push(log);
for key in filtered_mentioned_addresses.into_iter() {
transaction_log_collector
.mentioned_address_map
.entry(key)
.or_default()
.push(transaction_log_index);
}
}
}
}
fn collect_transaction_logs(
transaction_log_collector_config: &TransactionLogCollectorConfig,
transaction: &impl TransactionWithMeta,
execution_details: &TransactionExecutionDetails,
) -> Option<(TransactionLogInfo, Vec<Pubkey>)> {
let log_messages = execution_details.log_messages.as_ref()?;
let mut filtered_mentioned_addresses = Vec::new();
if !transaction_log_collector_config
.mentioned_addresses
.is_empty()
{
for key in transaction.account_keys().iter() {
if transaction_log_collector_config
.mentioned_addresses
.contains(key)
{
filtered_mentioned_addresses.push(*key);
}
}
}
let is_vote = transaction.is_simple_vote_transaction();
let store = match transaction_log_collector_config.filter {
TransactionLogCollectorFilter::All => {
!is_vote || !filtered_mentioned_addresses.is_empty()
}
TransactionLogCollectorFilter::AllWithVotes => true,
TransactionLogCollectorFilter::None => false,
TransactionLogCollectorFilter::OnlyMentionedAddresses => {
!filtered_mentioned_addresses.is_empty()
}
};
if store {
Some((
TransactionLogInfo {
signature: *transaction.signature(),
result: execution_details.status.clone(),
is_vote,
log_messages: log_messages.clone(),
},
filtered_mentioned_addresses,
))
} else {
None
}
}
pub fn load_accounts_data_size(&self) -> u64 {
self.accounts_data_size_initial
.saturating_add_signed(self.load_accounts_data_size_delta())
}
pub fn load_accounts_data_size_delta(&self) -> i64 {
let delta_on_chain = self.load_accounts_data_size_delta_on_chain();
let delta_off_chain = self.load_accounts_data_size_delta_off_chain();
delta_on_chain.saturating_add(delta_off_chain)
}
pub fn load_accounts_data_size_delta_on_chain(&self) -> i64 {
self.accounts_data_size_delta_on_chain.load(Acquire)
}
pub fn load_accounts_data_size_delta_off_chain(&self) -> i64 {
self.accounts_data_size_delta_off_chain.load(Acquire)
}
fn update_accounts_data_size_delta_on_chain(&self, amount: i64) {
if amount == 0 {
return;
}
self.accounts_data_size_delta_on_chain
.fetch_update(AcqRel, Acquire, |accounts_data_size_delta_on_chain| {
Some(accounts_data_size_delta_on_chain.saturating_add(amount))
})
.unwrap();
}
fn update_accounts_data_size_delta_off_chain(&self, amount: i64) {
if amount == 0 {
return;
}
self.accounts_data_size_delta_off_chain
.fetch_update(AcqRel, Acquire, |accounts_data_size_delta_off_chain| {
Some(accounts_data_size_delta_off_chain.saturating_add(amount))
})
.unwrap();
}
fn calculate_and_update_accounts_data_size_delta_off_chain(
&self,
old_data_size: usize,
new_data_size: usize,
) {
let data_size_delta = calculate_data_size_delta(old_data_size, new_data_size);
self.update_accounts_data_size_delta_off_chain(data_size_delta);
}
fn filter_program_errors_and_collect_fee_details(
&self,
processing_results: &[TransactionProcessingResult],
) {
let mut accumulated_fee_details = FeeDetails::default();
processing_results.iter().for_each(|processing_result| {
if let Ok(processed_tx) = processing_result {
accumulated_fee_details.accumulate(&processed_tx.fee_details());
}
});
self.collector_fee_details
.write()
.unwrap()
.accumulate(&accumulated_fee_details);
}
fn update_bank_hash_stats<'a>(&self, accounts: &impl StorableAccounts<'a>) {
let mut stats = BankHashStats::default();
(0..accounts.len()).for_each(|i| {
accounts.account(i, |account| {
stats.update(&account);
})
});
self.bank_hash_stats.accumulate(&stats);
}
pub fn commit_transactions(
&self,
sanitized_txs: &[impl TransactionWithMeta],
processing_results: Vec<TransactionProcessingResult>,
processed_counts: &ProcessedTransactionCounts,
timings: &mut ExecuteTimings,
) -> Vec<TransactionCommitResult> {
assert!(
!self.freeze_started(),
"commit_transactions() working on a bank that is already frozen or is undergoing \
freezing!"
);
let ProcessedTransactionCounts {
processed_transactions_count,
processed_non_vote_transactions_count,
processed_with_successful_result_count,
signature_count,
} = *processed_counts;
self.increment_transaction_count(processed_transactions_count);
self.increment_non_vote_transaction_count_since_restart(
processed_non_vote_transactions_count,
);
self.increment_signature_count(signature_count);
let processed_with_failure_result_count =
processed_transactions_count.saturating_sub(processed_with_successful_result_count);
self.transaction_error_count
.fetch_add(processed_with_failure_result_count, Relaxed);
if processed_transactions_count > 0 {
self.is_delta.store(true, Relaxed);
self.transaction_entries_count.fetch_add(1, Relaxed);
self.transactions_per_entry_max
.fetch_max(processed_transactions_count, Relaxed);
}
let ((), store_accounts_us) = measure_us!({
let maybe_transaction_refs = self
.accounts()
.accounts_db
.has_accounts_update_notifier()
.then(|| {
sanitized_txs
.iter()
.map(|tx| tx.as_sanitized_transaction())
.collect::<Vec<_>>()
});
let (accounts_to_store, transactions) = collect_accounts_to_store(
sanitized_txs,
&maybe_transaction_refs,
&processing_results,
);
let to_store = (self.slot(), accounts_to_store.as_slice());
self.update_bank_hash_stats(&to_store);
self.rc.accounts.store_accounts_seq(
to_store,
transactions.as_deref(),
Some(&self.ancestors),
);
});
let ((), update_stakes_cache_us) =
measure_us!(self.update_stakes_cache(sanitized_txs, &processing_results));
let ((), update_executors_us) = measure_us!({
let mut cache = None;
for processing_result in &processing_results {
if let Some(ProcessedTransaction::Executed(executed_tx)) =
processing_result.processed_transaction()
{
let programs_modified_by_tx = &executed_tx.programs_modified_by_tx;
if executed_tx.was_successful() && !programs_modified_by_tx.is_empty() {
cache
.get_or_insert_with(|| {
self.transaction_processor
.global_program_cache
.write()
.unwrap()
})
.merge(
&self.transaction_processor.environments,
self.slot,
programs_modified_by_tx,
);
}
}
}
});
let accounts_data_len_delta = processing_results
.iter()
.filter_map(|processing_result| processing_result.processed_transaction())
.filter_map(|processed_tx| processed_tx.execution_details())
.filter_map(|details| {
details
.status
.is_ok()
.then_some(details.accounts_data_len_delta)
})
.sum();
self.update_accounts_data_size_delta_on_chain(accounts_data_len_delta);
let ((), update_transaction_statuses_us) =
measure_us!(self.update_transaction_statuses(sanitized_txs, &processing_results));
self.filter_program_errors_and_collect_fee_details(&processing_results);
timings.saturating_add_in_place(ExecuteTimingType::StoreUs, store_accounts_us);
timings.saturating_add_in_place(
ExecuteTimingType::UpdateStakesCacheUs,
update_stakes_cache_us,
);
timings.saturating_add_in_place(ExecuteTimingType::UpdateExecutorsUs, update_executors_us);
timings.saturating_add_in_place(
ExecuteTimingType::UpdateTransactionStatuses,
update_transaction_statuses_us,
);
Self::create_commit_results(processing_results)
}
fn create_commit_results(
processing_results: Vec<TransactionProcessingResult>,
) -> Vec<TransactionCommitResult> {
processing_results
.into_iter()
.map(|processing_result| {
let processing_result = processing_result?;
let executed_units = processing_result.executed_units();
let loaded_accounts_data_size = processing_result.loaded_accounts_data_size();
match processing_result {
ProcessedTransaction::Executed(executed_tx) => {
let successful = executed_tx.was_successful();
let execution_details = executed_tx.execution_details;
let LoadedTransaction {
accounts: loaded_accounts,
fee_details,
rollback_accounts,
..
} = executed_tx.loaded_transaction;
let fee_payer_post_balance = if successful {
loaded_accounts[0].1.lamports()
} else {
rollback_accounts.fee_payer().1.lamports()
};
Ok(CommittedTransaction {
status: execution_details.status,
log_messages: execution_details.log_messages,
inner_instructions: execution_details.inner_instructions,
return_data: execution_details.return_data,
executed_units,
fee_details,
loaded_account_stats: TransactionLoadedAccountsStats {
loaded_accounts_count: loaded_accounts.len(),
loaded_accounts_data_size,
},
fee_payer_post_balance,
})
}
ProcessedTransaction::FeesOnly(fees_only_tx) => Ok(CommittedTransaction {
status: Err(fees_only_tx.load_error),
log_messages: None,
inner_instructions: None,
return_data: None,
executed_units,
fee_details: fees_only_tx.fee_details,
loaded_account_stats: TransactionLoadedAccountsStats {
loaded_accounts_count: fees_only_tx.rollback_accounts.count(),
loaded_accounts_data_size,
},
fee_payer_post_balance: fees_only_tx
.rollback_accounts
.fee_payer()
.1
.lamports(),
}),
}
})
.collect()
}
fn run_incinerator(&self) {
if let Some((account, _)) =
self.get_account_modified_since_parent_with_fixed_root(&incinerator::id())
{
self.capitalization.fetch_sub(account.lamports(), Relaxed);
self.store_account(&incinerator::id(), &AccountSharedData::default());
}
}
pub(crate) fn get_accounts_for_bank_hash_details(&self) -> Vec<(Pubkey, AccountSharedData)> {
let mut accounts = self
.rc
.accounts
.accounts_db
.get_pubkey_account_for_slot(self.slot());
accounts.sort_unstable_by_key(|a| a.0);
accounts
}
pub fn cluster_type(&self) -> ClusterType {
self.cluster_type.unwrap()
}
#[must_use]
pub fn load_execute_and_commit_transactions(
&self,
batch: &TransactionBatch<impl TransactionWithMeta>,
max_age: usize,
recording_config: ExecutionRecordingConfig,
timings: &mut ExecuteTimings,
log_messages_bytes_limit: Option<usize>,
) -> (Vec<TransactionCommitResult>, Option<BalanceCollector>) {
self.do_load_execute_and_commit_transactions_with_pre_commit_callback(
batch,
max_age,
recording_config,
timings,
log_messages_bytes_limit,
None::<fn(&mut _, &_) -> _>,
)
.unwrap()
}
pub fn load_execute_and_commit_transactions_with_pre_commit_callback<'a>(
&'a self,
batch: &TransactionBatch<impl TransactionWithMeta>,
max_age: usize,
recording_config: ExecutionRecordingConfig,
timings: &mut ExecuteTimings,
log_messages_bytes_limit: Option<usize>,
pre_commit_callback: impl FnOnce(
&mut ExecuteTimings,
&[TransactionProcessingResult],
) -> PreCommitResult<'a>,
) -> Result<(Vec<TransactionCommitResult>, Option<BalanceCollector>)> {
self.do_load_execute_and_commit_transactions_with_pre_commit_callback(
batch,
max_age,
recording_config,
timings,
log_messages_bytes_limit,
Some(pre_commit_callback),
)
}
fn do_load_execute_and_commit_transactions_with_pre_commit_callback<'a>(
&'a self,
batch: &TransactionBatch<impl TransactionWithMeta>,
max_age: usize,
recording_config: ExecutionRecordingConfig,
timings: &mut ExecuteTimings,
log_messages_bytes_limit: Option<usize>,
pre_commit_callback: Option<
impl FnOnce(&mut ExecuteTimings, &[TransactionProcessingResult]) -> PreCommitResult<'a>,
>,
) -> Result<(Vec<TransactionCommitResult>, Option<BalanceCollector>)> {
let LoadAndExecuteTransactionsOutput {
processing_results,
processed_counts,
balance_collector,
} = self.load_and_execute_transactions(
batch,
max_age,
timings,
&mut TransactionErrorMetrics::default(),
TransactionProcessingConfig {
account_overrides: None,
check_program_deployment_slot: self.check_program_deployment_slot,
log_messages_bytes_limit,
limit_to_load_programs: false,
recording_config,
drop_on_failure: false,
all_or_nothing: false,
},
);
let freeze_lock = if let Some(pre_commit_callback) = pre_commit_callback {
pre_commit_callback(timings, &processing_results)?
} else {
None
};
let commit_results = self.commit_transactions(
batch.sanitized_transactions(),
processing_results,
&processed_counts,
timings,
);
drop(freeze_lock);
Ok((commit_results, balance_collector))
}
pub fn process_transaction(&self, tx: &Transaction) -> Result<()> {
self.try_process_transactions(std::iter::once(tx))?[0].clone()
}
pub fn process_transaction_with_metadata(
&self,
tx: impl Into<VersionedTransaction>,
) -> Result<CommittedTransaction> {
let txs = vec![tx.into()];
let batch = self.prepare_entry_batch(txs)?;
let (mut commit_results, ..) = self.load_execute_and_commit_transactions(
&batch,
MAX_PROCESSING_AGE,
ExecutionRecordingConfig {
enable_cpi_recording: false,
enable_log_recording: true,
enable_return_data_recording: true,
enable_transaction_balance_recording: false,
},
&mut ExecuteTimings::default(),
Some(1000 * 1000),
);
commit_results.remove(0)
}
pub fn try_process_transactions<'a>(
&self,
txs: impl Iterator<Item = &'a Transaction>,
) -> Result<Vec<Result<()>>> {
let txs = txs
.map(|tx| VersionedTransaction::from(tx.clone()))
.collect();
self.try_process_entry_transactions(txs)
}
pub fn try_process_entry_transactions(
&self,
txs: Vec<VersionedTransaction>,
) -> Result<Vec<Result<()>>> {
let batch = self.prepare_entry_batch(txs)?;
Ok(self.process_transaction_batch(&batch))
}
#[must_use]
fn process_transaction_batch(
&self,
batch: &TransactionBatch<impl TransactionWithMeta>,
) -> Vec<Result<()>> {
self.load_execute_and_commit_transactions(
batch,
MAX_PROCESSING_AGE,
ExecutionRecordingConfig::new_single_setting(false),
&mut ExecuteTimings::default(),
None,
)
.0
.into_iter()
.map(|commit_result| commit_result.and_then(|committed_tx| committed_tx.status))
.collect()
}
pub fn transfer(&self, n: u64, keypair: &Keypair, to: &Pubkey) -> Result<Signature> {
let blockhash = self.last_blockhash();
let tx = system_transaction::transfer(keypair, to, n, blockhash);
let signature = tx.signatures[0];
self.process_transaction(&tx).map(|_| signature)
}
pub fn read_balance(account: &AccountSharedData) -> u64 {
account.lamports()
}
pub fn get_balance(&self, pubkey: &Pubkey) -> u64 {
self.get_account(pubkey)
.map(|x| Self::read_balance(&x))
.unwrap_or(0)
}
pub fn parents(&self) -> Vec<Arc<Bank>> {
let mut parents = vec![];
let mut bank = self.parent();
while let Some(parent) = bank {
parents.push(parent.clone());
bank = parent.parent();
}
parents
}
pub fn parents_inclusive(self: Arc<Self>) -> Vec<Arc<Bank>> {
let mut parents = self.parents();
parents.insert(0, self);
parents
}
pub fn store_account(&self, pubkey: &Pubkey, account: &AccountSharedData) {
self.store_accounts((self.slot(), &[(pubkey, account)][..]))
}
pub fn store_accounts<'a>(&self, accounts: impl StorableAccounts<'a>) {
assert!(!self.freeze_started());
let mut m = Measure::start("stakes_cache.check_and_store");
let new_warmup_cooldown_rate_epoch = self.new_warmup_cooldown_rate_epoch();
(0..accounts.len()).for_each(|i| {
accounts.account(i, |account| {
self.stakes_cache.check_and_store(
account.pubkey(),
&account,
new_warmup_cooldown_rate_epoch,
)
})
});
self.update_bank_hash_stats(&accounts);
self.rc
.accounts
.store_accounts_par(accounts, None, Some(&self.ancestors));
m.stop();
self.rc
.accounts
.accounts_db
.stats
.stakes_cache_check_and_store_us
.fetch_add(m.as_us(), Relaxed);
}
pub fn force_flush_accounts_cache(&self) {
self.rc
.accounts
.accounts_db
.flush_accounts_cache(true, Some(self.slot()))
}
fn store_account_and_update_capitalization(
&self,
pubkey: &Pubkey,
new_account: &AccountSharedData,
) {
let old_account_data_size = if let Some(old_account) =
self.get_account_with_fixed_root_no_cache(pubkey)
{
match new_account.lamports().cmp(&old_account.lamports()) {
std::cmp::Ordering::Greater => {
let diff = new_account.lamports() - old_account.lamports();
trace!("store_account_and_update_capitalization: increased: {pubkey} {diff}");
self.capitalization.fetch_add(diff, Relaxed);
}
std::cmp::Ordering::Less => {
let diff = old_account.lamports() - new_account.lamports();
trace!("store_account_and_update_capitalization: decreased: {pubkey} {diff}");
self.capitalization.fetch_sub(diff, Relaxed);
}
std::cmp::Ordering::Equal => {}
}
old_account.data().len()
} else {
trace!(
"store_account_and_update_capitalization: created: {pubkey} {}",
new_account.lamports()
);
self.capitalization
.fetch_add(new_account.lamports(), Relaxed);
0
};
self.store_account(pubkey, new_account);
let new_account_data_size = if new_account.lamports() == 0 {
0
} else {
new_account.data().len()
};
self.calculate_and_update_accounts_data_size_delta_off_chain(
old_account_data_size,
new_account_data_size,
);
}
pub fn accounts(&self) -> Arc<Accounts> {
self.rc.accounts.clone()
}
fn apply_simd_0306_cost_tracker_changes(&mut self) {
let mut cost_tracker = self.write_cost_tracker().unwrap();
let block_cost_limit = cost_tracker.get_block_limit();
let vote_cost_limit = cost_tracker.get_vote_limit();
let account_cost_limit = block_cost_limit.saturating_mul(40).saturating_div(100);
cost_tracker.set_limits(account_cost_limit, block_cost_limit, vote_cost_limit);
}
fn apply_simd_0339_invoke_cost_changes(&mut self) {
let simd_0268_active = self
.feature_set
.is_active(&raise_cpi_nesting_limit_to_8::id());
let simd_0339_active = self
.feature_set
.is_active(&increase_cpi_account_info_limit::id());
let compute_budget = self
.compute_budget()
.as_ref()
.unwrap_or(&ComputeBudget::new_with_defaults(
simd_0268_active,
simd_0339_active,
))
.to_cost();
self.transaction_processor
.set_execution_cost(compute_budget);
}
fn apply_activated_features(&mut self) {
self.reserved_account_keys = {
let mut reserved_keys = ReservedAccountKeys::clone(&self.reserved_account_keys);
reserved_keys.update_active_set(&self.feature_set);
Arc::new(reserved_keys)
};
self.add_active_builtin_programs();
if self
.feature_set
.is_active(&feature_set::raise_block_limits_to_100m::id())
{
let block_cost_limit = simd_0286_block_limits();
let mut cost_tracker = self.write_cost_tracker().unwrap();
let account_cost_limit = cost_tracker.get_account_limit();
let vote_cost_limit = cost_tracker.get_vote_limit();
cost_tracker.set_limits(account_cost_limit, block_cost_limit, vote_cost_limit);
}
if self
.feature_set
.is_active(&feature_set::raise_account_cu_limit::id())
{
self.apply_simd_0306_cost_tracker_changes();
}
if self
.feature_set
.is_active(&feature_set::increase_cpi_account_info_limit::id())
{
self.apply_simd_0339_invoke_cost_changes();
}
let environments = self.create_program_runtime_environments(&self.feature_set);
self.transaction_processor
.global_program_cache
.write()
.unwrap()
.latest_root_slot = self.slot;
self.transaction_processor
.epoch_boundary_preparation
.write()
.unwrap()
.upcoming_epoch = self.epoch;
self.transaction_processor.environments = environments;
}
fn create_program_runtime_environments(
&self,
feature_set: &FeatureSet,
) -> ProgramRuntimeEnvironments {
let simd_0268_active = feature_set.is_active(&raise_cpi_nesting_limit_to_8::id());
let simd_0339_active = feature_set.is_active(&increase_cpi_account_info_limit::id());
let compute_budget = self
.compute_budget()
.as_ref()
.unwrap_or(&ComputeBudget::new_with_defaults(
simd_0268_active,
simd_0339_active,
))
.to_budget();
ProgramRuntimeEnvironments {
program_runtime_v1: Arc::new(
create_program_runtime_environment_v1(
&feature_set.runtime_features(),
&compute_budget,
false,
false,
)
.unwrap(),
),
program_runtime_v2: Arc::new(create_program_runtime_environment_v2(
&compute_budget,
false,
)),
}
}
pub fn set_tick_height(&self, tick_height: u64) {
self.tick_height.store(tick_height, Relaxed)
}
pub fn set_inflation(&self, inflation: Inflation) {
*self.inflation.write().unwrap() = inflation;
}
pub fn hard_forks(&self) -> HardForks {
self.hard_forks.read().unwrap().clone()
}
pub fn register_hard_fork(&self, new_hard_fork_slot: Slot) {
let bank_slot = self.slot();
let lock = self.freeze_lock();
let bank_frozen = *lock != Hash::default();
if new_hard_fork_slot < bank_slot {
warn!(
"Hard fork at slot {new_hard_fork_slot} ignored, the hard fork is older than the \
bank at slot {bank_slot} that attempted to register it."
);
} else if (new_hard_fork_slot == bank_slot) && bank_frozen {
warn!(
"Hard fork at slot {new_hard_fork_slot} ignored, the hard fork is the same slot \
as the bank at slot {bank_slot} that attempted to register it, but that bank is \
already frozen."
);
} else {
self.hard_forks
.write()
.unwrap()
.register(new_hard_fork_slot);
}
}
pub fn register_hard_forks(&self, new_hard_fork_slots: Option<&Vec<Slot>>) {
if let Some(slots) = new_hard_fork_slots {
slots
.iter()
.for_each(|hard_fork_slot| self.register_hard_fork(*hard_fork_slot));
}
}
pub fn get_account_with_fixed_root_no_cache(
&self,
pubkey: &Pubkey,
) -> Option<AccountSharedData> {
self.rc
.accounts
.load_with_fixed_root_do_not_populate_read_cache(&self.ancestors, pubkey)
.map(|(acc, _slot)| acc)
}
pub fn get_account(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
self.get_account_modified_slot(pubkey)
.map(|(acc, _slot)| acc)
}
pub fn get_account_with_fixed_root(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
self.get_account_modified_slot_with_fixed_root(pubkey)
.map(|(acc, _slot)| acc)
}
pub fn get_account_modified_slot_with_fixed_root(
&self,
pubkey: &Pubkey,
) -> Option<(AccountSharedData, Slot)> {
self.load_slow_with_fixed_root(&self.ancestors, pubkey)
}
pub fn get_account_modified_slot(&self, pubkey: &Pubkey) -> Option<(AccountSharedData, Slot)> {
self.load_slow(&self.ancestors, pubkey)
}
fn load_slow(
&self,
ancestors: &Ancestors,
pubkey: &Pubkey,
) -> Option<(AccountSharedData, Slot)> {
self.rc.accounts.load_without_fixed_root(ancestors, pubkey)
}
fn load_slow_with_fixed_root(
&self,
ancestors: &Ancestors,
pubkey: &Pubkey,
) -> Option<(AccountSharedData, Slot)> {
self.rc.accounts.load_with_fixed_root(ancestors, pubkey)
}
pub fn get_program_accounts(
&self,
program_id: &Pubkey,
config: &ScanConfig,
) -> ScanResult<Vec<KeyedAccountSharedData>> {
self.rc
.accounts
.load_by_program(&self.ancestors, self.bank_id, program_id, config)
}
pub fn get_filtered_program_accounts<F: Fn(&AccountSharedData) -> bool>(
&self,
program_id: &Pubkey,
filter: F,
config: &ScanConfig,
) -> ScanResult<Vec<KeyedAccountSharedData>> {
self.rc.accounts.load_by_program_with_filter(
&self.ancestors,
self.bank_id,
program_id,
filter,
config,
)
}
pub fn get_filtered_indexed_accounts<F: Fn(&AccountSharedData) -> bool>(
&self,
index_key: &IndexKey,
filter: F,
config: &ScanConfig,
byte_limit_for_scan: Option<usize>,
) -> ScanResult<Vec<KeyedAccountSharedData>> {
self.rc.accounts.load_by_index_key_with_filter(
&self.ancestors,
self.bank_id,
index_key,
filter,
config,
byte_limit_for_scan,
)
}
pub fn account_indexes_include_key(&self, key: &Pubkey) -> bool {
self.rc.accounts.account_indexes_include_key(key)
}
pub fn get_all_accounts(&self, sort_results: bool) -> ScanResult<Vec<PubkeyAccountSlot>> {
self.rc
.accounts
.load_all(&self.ancestors, self.bank_id, sort_results)
}
pub fn scan_all_accounts<F>(&self, scan_func: F, sort_results: bool) -> ScanResult<()>
where
F: FnMut(Option<(&Pubkey, AccountSharedData, Slot)>),
{
self.rc
.accounts
.scan_all(&self.ancestors, self.bank_id, scan_func, sort_results)
}
pub fn get_program_accounts_modified_since_parent(
&self,
program_id: &Pubkey,
) -> Vec<KeyedAccountSharedData> {
self.rc
.accounts
.load_by_program_slot(self.slot(), Some(program_id))
}
pub fn get_transaction_logs(
&self,
address: Option<&Pubkey>,
) -> Option<Vec<TransactionLogInfo>> {
self.transaction_log_collector
.read()
.unwrap()
.get_logs_for_address(address)
}
pub fn get_all_accounts_modified_since_parent(&self) -> Vec<KeyedAccountSharedData> {
self.rc.accounts.load_by_program_slot(self.slot(), None)
}
fn get_account_modified_since_parent_with_fixed_root(
&self,
pubkey: &Pubkey,
) -> Option<(AccountSharedData, Slot)> {
let just_self: Ancestors = Ancestors::from(vec![self.slot()]);
if let Some((account, slot)) = self.load_slow_with_fixed_root(&just_self, pubkey) {
if slot == self.slot() {
return Some((account, slot));
}
}
None
}
pub fn get_largest_accounts(
&self,
num: usize,
filter_by_address: &HashSet<Pubkey>,
filter: AccountAddressFilter,
sort_results: bool,
) -> ScanResult<Vec<(Pubkey, u64)>> {
self.rc.accounts.load_largest_accounts(
&self.ancestors,
self.bank_id,
num,
filter_by_address,
filter,
sort_results,
)
}
pub fn transaction_count(&self) -> u64 {
self.transaction_count.load(Relaxed)
}
pub fn non_vote_transaction_count_since_restart(&self) -> u64 {
self.non_vote_transaction_count_since_restart.load(Relaxed)
}
pub fn executed_transaction_count(&self) -> u64 {
self.transaction_count()
.saturating_sub(self.parent().map_or(0, |parent| parent.transaction_count()))
}
pub fn transaction_error_count(&self) -> u64 {
self.transaction_error_count.load(Relaxed)
}
pub fn transaction_entries_count(&self) -> u64 {
self.transaction_entries_count.load(Relaxed)
}
pub fn transactions_per_entry_max(&self) -> u64 {
self.transactions_per_entry_max.load(Relaxed)
}
pub fn entry_bytes_budget(&self) -> &EntryBytesBudget {
&self.entry_bytes_consumed
}
fn increment_transaction_count(&self, tx_count: u64) {
self.transaction_count.fetch_add(tx_count, Relaxed);
}
fn increment_non_vote_transaction_count_since_restart(&self, tx_count: u64) {
self.non_vote_transaction_count_since_restart
.fetch_add(tx_count, Relaxed);
}
pub fn signature_count(&self) -> u64 {
self.signature_count.load(Relaxed)
}
fn increment_signature_count(&self, signature_count: u64) {
self.signature_count.fetch_add(signature_count, Relaxed);
}
pub fn get_signature_status_processed_since_parent(
&self,
signature: &Signature,
) -> Option<Result<()>> {
if let Some((slot, status)) = self.get_signature_status_slot(signature) {
if slot <= self.slot() {
return Some(status);
}
}
None
}
pub fn get_signature_status_with_blockhash(
&self,
signature: &Signature,
blockhash: &Hash,
) -> Option<Result<()>> {
let rcache = self.status_cache.read().unwrap();
rcache
.get_status(signature, blockhash, &self.ancestors)
.map(|v| v.1)
}
pub fn get_committed_transaction_status_and_slot(
&self,
message_hash: &Hash,
transaction_blockhash: &Hash,
) -> Option<(Slot, bool)> {
let rcache = self.status_cache.read().unwrap();
rcache
.get_status(message_hash, transaction_blockhash, &self.ancestors)
.map(|(slot, status)| (slot, status.is_ok()))
}
pub fn get_signature_status_slot(&self, signature: &Signature) -> Option<(Slot, Result<()>)> {
let rcache = self.status_cache.read().unwrap();
rcache.get_status_any_blockhash(signature, &self.ancestors)
}
pub fn get_signature_status(&self, signature: &Signature) -> Option<Result<()>> {
self.get_signature_status_slot(signature).map(|v| v.1)
}
pub fn has_signature(&self, signature: &Signature) -> bool {
self.get_signature_status_slot(signature).is_some()
}
fn hash_internal_state(&self) -> Hash {
let measure_total = Measure::start("");
let slot = self.slot();
let mut hash = hashv(&[
self.parent_hash.as_ref(),
&self.signature_count().to_le_bytes(),
self.last_blockhash().as_ref(),
]);
let accounts_lt_hash_checksum = {
let accounts_lt_hash = &*self.accounts_lt_hash.lock().unwrap();
let lt_hash_bytes = bytemuck::must_cast_slice(&accounts_lt_hash.0.0);
hash = hashv(&[hash.as_ref(), lt_hash_bytes]);
accounts_lt_hash.0.checksum()
};
let buf = self
.hard_forks
.read()
.unwrap()
.get_hash_data(slot, self.parent_slot());
if let Some(buf) = buf {
let hard_forked_hash = hashv(&[hash.as_ref(), &buf]);
warn!("hard fork at slot {slot} by hashing {buf:?}: {hash} => {hard_forked_hash}");
hash = hard_forked_hash;
}
#[cfg(feature = "dev-context-only-utils")]
let hash_override = self
.hash_overrides
.lock()
.unwrap()
.get_bank_hash_override(slot)
.copied()
.inspect(|&hash_override| {
if hash_override != hash {
info!(
"bank: slot: {}: overrode bank hash: {} with {}",
self.slot(),
hash,
hash_override
);
}
});
#[cfg(feature = "dev-context-only-utils")]
let hash = hash_override.unwrap_or(std::hint::black_box(hash));
let bank_hash_stats = self.bank_hash_stats.load();
let total_us = measure_total.end_as_us();
datapoint_info!(
"bank-hash_internal_state",
("slot", slot, i64),
("total_us", total_us, i64),
);
info!(
"bank frozen: {slot} hash: {hash} signature_count: {} last_blockhash: {} \
capitalization: {}, accounts_lt_hash checksum: {accounts_lt_hash_checksum}, stats: \
{bank_hash_stats:?}",
self.signature_count(),
self.last_blockhash(),
self.capitalization(),
);
hash
}
pub fn run_final_hash_calc(&self) {
self.force_flush_accounts_cache();
_ = self.verify_accounts(
VerifyAccountsHashConfig {
require_rooted_bank: false,
},
None,
);
}
#[must_use]
fn verify_accounts(
&self,
config: VerifyAccountsHashConfig,
calculated_accounts_lt_hash: Option<&AccountsLtHash>,
) -> bool {
let accounts_db = &self.rc.accounts.accounts_db;
let slot = self.slot();
if config.require_rooted_bank && !accounts_db.accounts_index.is_alive_root(slot) {
if let Some(parent) = self.parent() {
info!(
"slot {slot} is not a root, so verify accounts hash on parent bank at slot {}",
parent.slot(),
);
return parent.verify_accounts(config, None);
} else {
panic!("cannot verify accounts hash because slot {slot} is not a root");
}
}
fn check_lt_hash(
expected_accounts_lt_hash: &AccountsLtHash,
calculated_accounts_lt_hash: &AccountsLtHash,
) -> bool {
let is_ok = calculated_accounts_lt_hash == expected_accounts_lt_hash;
if !is_ok {
let expected = expected_accounts_lt_hash.0.checksum();
let calculated = calculated_accounts_lt_hash.0.checksum();
error!(
"Verifying accounts failed: accounts lattice hashes do not match, expected: \
{expected}, calculated: {calculated}",
);
}
is_ok
}
info!("Verifying accounts...");
let start = Instant::now();
let expected_accounts_lt_hash = self.accounts_lt_hash.lock().unwrap().clone();
let is_ok = if let Some(calculated_accounts_lt_hash) = calculated_accounts_lt_hash {
check_lt_hash(&expected_accounts_lt_hash, calculated_accounts_lt_hash)
} else {
let calculated_accounts_lt_hash =
accounts_db.calculate_accounts_lt_hash_at_startup_from_index(&self.ancestors, slot);
check_lt_hash(&expected_accounts_lt_hash, &calculated_accounts_lt_hash)
};
info!("Verifying accounts... Done in {:?}", start.elapsed());
is_ok
}
pub fn get_snapshot_storages(&self, base_slot: Option<Slot>) -> Vec<Arc<AccountStorageEntry>> {
let start_slot = base_slot.map_or(0, |slot| slot.saturating_add(1));
let requested_slots = start_slot..=self.slot();
self.rc.accounts.accounts_db.get_storages(requested_slots).0
}
#[must_use]
fn verify_hash(&self) -> bool {
assert!(self.is_frozen());
let calculated_hash = self.hash_internal_state();
let expected_hash = self.hash();
if calculated_hash == expected_hash {
true
} else {
warn!(
"verify failed: slot: {}, {} (calculated) != {} (expected)",
self.slot(),
calculated_hash,
expected_hash
);
false
}
}
pub fn verify_transaction(
&self,
tx: VersionedTransaction,
verification_mode: TransactionVerificationMode,
) -> Result<RuntimeTransaction<SanitizedTransaction>> {
let serialized_message = tx.message.serialize();
self.verify_transaction_with_serialized_message(tx, &serialized_message, verification_mode)
}
pub fn verify_transaction_with_serialized_message(
&self,
tx: VersionedTransaction,
serialized_message: &[u8],
verification_mode: TransactionVerificationMode,
) -> Result<RuntimeTransaction<SanitizedTransaction>> {
let enable_static_instruction_limit = self
.feature_set
.is_active(&agave_feature_set::static_instruction_limit::id());
let enable_instruction_account_limit = self
.feature_set
.is_active(&agave_feature_set::limit_instruction_accounts::id());
let sanitized_tx = {
let size =
bincode::serialized_size(&tx).map_err(|_| TransactionError::SanitizeFailure)?;
if size > PACKET_DATA_SIZE as u64 {
return Err(TransactionError::SanitizeFailure);
}
if enable_static_instruction_limit
&& tx.message.instructions().len()
> solana_transaction_context::MAX_INSTRUCTION_TRACE_LENGTH
{
return Err(solana_transaction_error::TransactionError::SanitizeFailure);
}
let message_hash = if verification_mode == TransactionVerificationMode::FullVerification
{
tx.verify_and_hash_message()?
} else {
VersionedMessage::hash_raw_message(serialized_message)
};
RuntimeTransaction::try_create(
tx,
MessageHash::Precomputed(message_hash),
None,
self,
self.get_reserved_account_keys(),
enable_static_instruction_limit,
enable_instruction_account_limit,
)
}?;
Ok(sanitized_tx)
}
pub fn check_reserved_keys(&self, tx: &impl SVMMessage) -> Result<()> {
let reserved_keys = self.get_reserved_account_keys();
for (index, key) in tx.account_keys().iter().enumerate() {
if tx.is_writable(index) && reserved_keys.contains(key) {
return Err(TransactionError::ResanitizationNeeded);
}
}
Ok(())
}
pub fn calculate_capitalization_for_tests(&self) -> u64 {
self.rc
.accounts
.accounts_db
.calculate_capitalization_at_startup_from_index(&self.ancestors, self.slot())
}
pub fn set_capitalization_for_tests(&self, capitalization: u64) {
self.capitalization.store(capitalization, Relaxed);
}
pub fn get_snapshot_hash(&self) -> SnapshotHash {
SnapshotHash::new(self.accounts_lt_hash.lock().unwrap().0.checksum())
}
pub fn verify_snapshot_bank(
&self,
skip_shrink: bool,
force_clean: bool,
latest_full_snapshot_slot: Slot,
calculated_accounts_lt_hash: Option<&AccountsLtHash>,
) -> bool {
let (verified_accounts, verify_accounts_time_us) = measure_us!({
let should_verify_accounts = !self.rc.accounts.accounts_db.skip_initial_hash_calc;
if should_verify_accounts {
self.verify_accounts(
VerifyAccountsHashConfig {
require_rooted_bank: false,
},
calculated_accounts_lt_hash,
)
} else {
info!("Verifying accounts... Skipped.");
true
}
});
let (_, clean_time_us) = measure_us!({
let should_clean = force_clean || (!skip_shrink && self.slot() > 0);
if should_clean {
info!("Cleaning...");
self.rc
.accounts
.accounts_db
.clean_accounts(Some(latest_full_snapshot_slot), true);
info!("Cleaning... Done.");
} else {
info!("Cleaning... Skipped.");
}
});
let (_, shrink_time_us) = measure_us!({
let should_shrink = !skip_shrink && self.slot() > 0;
if should_shrink {
info!("Shrinking...");
self.rc.accounts.accounts_db.shrink_all_slots(
true,
Some(self.slot()),
);
info!("Shrinking... Done.");
} else {
info!("Shrinking... Skipped.");
}
});
info!("Verifying bank...");
let (verified_bank, verify_bank_time_us) = measure_us!(self.verify_hash());
info!("Verifying bank... Done.");
datapoint_info!(
"verify_snapshot_bank",
("clean_us", clean_time_us, i64),
("shrink_us", shrink_time_us, i64),
("verify_accounts_us", verify_accounts_time_us, i64),
("verify_bank_us", verify_bank_time_us, i64),
);
verified_accounts && verified_bank
}
pub fn hashes_per_tick(&self) -> &Option<u64> {
&self.hashes_per_tick
}
pub fn ticks_per_slot(&self) -> u64 {
self.ticks_per_slot
}
pub fn slots_per_year(&self) -> f64 {
self.slots_per_year
}
pub fn tick_height(&self) -> u64 {
self.tick_height.load(Relaxed)
}
pub fn inflation(&self) -> Inflation {
*self.inflation.read().unwrap()
}
pub fn rent_collector(&self) -> &RentCollector {
&self.rent_collector
}
pub fn capitalization(&self) -> u64 {
self.capitalization.load(Relaxed)
}
pub fn max_tick_height(&self) -> u64 {
self.max_tick_height
}
pub fn block_height(&self) -> u64 {
self.block_height
}
pub fn get_slots_in_epoch(&self, epoch: Epoch) -> u64 {
self.epoch_schedule().get_slots_in_epoch(epoch)
}
pub fn get_leader_schedule_epoch(&self, slot: Slot) -> Epoch {
self.epoch_schedule().get_leader_schedule_epoch(slot)
}
fn update_stakes_cache(
&self,
txs: &[impl SVMMessage],
processing_results: &[TransactionProcessingResult],
) {
debug_assert_eq!(txs.len(), processing_results.len());
let new_warmup_cooldown_rate_epoch = self.new_warmup_cooldown_rate_epoch();
txs.iter()
.zip(processing_results)
.filter_map(|(tx, processing_result)| {
processing_result
.processed_transaction()
.map(|processed_tx| (tx, processed_tx))
})
.filter_map(|(tx, processed_tx)| {
processed_tx
.executed_transaction()
.map(|executed_tx| (tx, executed_tx))
})
.filter(|(_, executed_tx)| executed_tx.was_successful())
.flat_map(|(tx, executed_tx)| {
let num_account_keys = tx.account_keys().len();
let loaded_tx = &executed_tx.loaded_transaction;
loaded_tx.accounts.iter().take(num_account_keys)
})
.for_each(|(pubkey, account)| {
self.stakes_cache
.check_and_store(pubkey, account, new_warmup_cooldown_rate_epoch);
});
}
pub fn vote_accounts(&self) -> Arc<VoteAccountsHashMap> {
let stakes = self.stakes_cache.stakes();
Arc::from(stakes.vote_accounts())
}
pub fn get_vote_account(&self, vote_account: &Pubkey) -> Option<VoteAccount> {
let stakes = self.stakes_cache.stakes();
let vote_account = stakes.vote_accounts().get(vote_account)?;
Some(vote_account.clone())
}
pub fn current_epoch_stakes(&self) -> &VersionedEpochStakes {
self.epoch_stakes
.get(&self.epoch.saturating_add(1))
.expect("Current epoch stakes must exist")
}
pub fn epoch_stakes(&self, epoch: Epoch) -> Option<&VersionedEpochStakes> {
self.epoch_stakes.get(&epoch)
}
pub fn verify_certificate(
&self,
cert: &Certificate,
) -> std::result::Result<(u64, u64), CertVerifyError> {
let slot = cert.cert_type.slot();
let epoch_stakes = self
.epoch_stakes_from_slot(slot)
.ok_or(CertVerifyError::MissingRankMap)?;
let key_to_rank_map = epoch_stakes.bls_pubkey_to_rank_map();
let total_stake = epoch_stakes.total_stake();
let stake = cert_verify::verify_certificate(cert, key_to_rank_map.len(), |rank| {
key_to_rank_map
.get_pubkey_stake_entry(rank)
.map(|entry| (entry.stake, entry.bls_pubkey))
})?;
Ok((stake, total_stake))
}
pub fn epoch_stakes_map(&self) -> &HashMap<Epoch, VersionedEpochStakes> {
&self.epoch_stakes
}
pub fn current_epoch_staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
self.current_epoch_stakes().stakes().staked_nodes()
}
pub fn epoch_staked_nodes(&self, epoch: Epoch) -> Option<Arc<HashMap<Pubkey, u64>>> {
Some(self.epoch_stakes.get(&epoch)?.stakes().staked_nodes())
}
pub fn epoch_total_stake(&self, epoch: Epoch) -> Option<u64> {
self.epoch_stakes
.get(&epoch)
.map(|epoch_stakes| epoch_stakes.total_stake())
}
pub fn get_current_epoch_total_stake(&self) -> u64 {
self.current_epoch_stakes().total_stake()
}
pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&VoteAccountsHashMap> {
let epoch_stakes = self.epoch_stakes.get(&epoch)?.stakes();
Some(epoch_stakes.vote_accounts().as_ref())
}
pub fn get_current_epoch_vote_accounts(&self) -> &VoteAccountsHashMap {
self.current_epoch_stakes()
.stakes()
.vote_accounts()
.as_ref()
}
pub fn epoch_authorized_voter(&self, vote_account: &Pubkey) -> Option<&Pubkey> {
self.epoch_stakes
.get(&self.epoch)
.expect("Epoch stakes for bank's own epoch must exist")
.epoch_authorized_voters()
.get(vote_account)
}
pub fn epoch_vote_accounts_for_node_id(&self, node_id: &Pubkey) -> Option<&NodeVoteAccounts> {
self.epoch_stakes
.get(&self.epoch)
.expect("Epoch stakes for bank's own epoch must exist")
.node_id_to_vote_accounts()
.get(node_id)
}
pub fn epoch_node_id_to_stake(&self, epoch: Epoch, node_id: &Pubkey) -> Option<u64> {
self.epoch_stakes(epoch)
.and_then(|epoch_stakes| epoch_stakes.node_id_to_stake(node_id))
}
pub fn total_epoch_stake(&self) -> u64 {
self.epoch_stakes
.get(&self.epoch)
.expect("Epoch stakes for bank's own epoch must exist")
.total_stake()
}
pub fn epoch_vote_account_stake(&self, vote_account: &Pubkey) -> u64 {
*self
.epoch_vote_accounts(self.epoch())
.expect("Bank epoch vote accounts must contain entry for the bank's own epoch")
.get(vote_account)
.map(|(stake, _)| stake)
.unwrap_or(&0)
}
pub fn get_epoch_and_slot_index(&self, slot: Slot) -> (Epoch, SlotIndex) {
self.epoch_schedule().get_epoch_and_slot_index(slot)
}
pub fn get_epoch_info(&self) -> EpochInfo {
let absolute_slot = self.slot();
let block_height = self.block_height();
let (epoch, slot_index) = self.get_epoch_and_slot_index(absolute_slot);
let slots_in_epoch = self.get_slots_in_epoch(epoch);
let transaction_count = Some(self.transaction_count());
EpochInfo {
epoch,
slot_index,
slots_in_epoch,
absolute_slot,
block_height,
transaction_count,
}
}
pub fn is_empty(&self) -> bool {
!self.is_delta.load(Relaxed)
}
pub fn add_mockup_builtin(
&mut self,
program_id: Pubkey,
builtin_function: BuiltinFunctionWithContext,
) {
self.add_builtin(
program_id,
"mockup",
ProgramCacheEntry::new_builtin(self.slot, 0, builtin_function),
);
}
pub fn add_precompile(&mut self, program_id: &Pubkey) {
debug!("Adding precompiled program {program_id}");
self.add_precompiled_account(program_id);
debug!("Added precompiled program {program_id:?}");
}
pub(crate) fn clean_accounts(&self) {
let highest_slot_to_clean = self.slot().saturating_sub(1);
self.rc
.accounts
.accounts_db
.clean_accounts(Some(highest_slot_to_clean), false);
}
pub fn print_accounts_stats(&self) {
self.rc.accounts.accounts_db.print_accounts_stats("");
}
pub fn shrink_candidate_slots(&self) -> usize {
self.rc
.accounts
.accounts_db
.shrink_candidate_slots(self.epoch_schedule())
}
pub(crate) fn shrink_ancient_slots(&self) {
self.rc
.accounts
.accounts_db
.shrink_ancient_slots(self.epoch_schedule())
}
pub fn read_cost_tracker(&self) -> LockResult<RwLockReadGuard<'_, CostTracker>> {
self.cost_tracker.read()
}
pub fn write_cost_tracker(&self) -> LockResult<RwLockWriteGuard<'_, CostTracker>> {
self.cost_tracker.write()
}
pub fn should_bank_still_be_processing_txs(
bank_creation_time: &Instant,
max_tx_ingestion_nanos: u128,
) -> bool {
bank_creation_time.elapsed().as_nanos() <= max_tx_ingestion_nanos
}
pub fn deactivate_feature(&mut self, id: &Pubkey) {
let mut feature_set = Arc::make_mut(&mut self.feature_set).clone();
feature_set.active_mut().remove(id);
feature_set.inactive_mut().insert(*id);
self.feature_set = Arc::new(feature_set);
}
pub fn activate_feature(&mut self, id: &Pubkey) {
let mut feature_set = Arc::make_mut(&mut self.feature_set).clone();
feature_set.inactive_mut().remove(id);
feature_set.active_mut().insert(*id, 0);
self.feature_set = Arc::new(feature_set);
}
pub fn fill_bank_with_ticks_for_tests(&self) {
self.do_fill_bank_with_ticks_for_tests(&BankWithScheduler::no_scheduler_available())
}
pub(crate) fn do_fill_bank_with_ticks_for_tests(&self, scheduler: &InstalledSchedulerRwLock) {
if self.tick_height.load(Relaxed) < self.max_tick_height {
let last_blockhash = self.last_blockhash();
while self.last_blockhash() == last_blockhash {
self.register_tick(&Hash::new_unique(), scheduler)
}
} else {
warn!("Bank already reached max tick height, cannot fill it with more ticks");
}
}
pub fn get_reserved_account_keys(&self) -> &HashSet<Pubkey> {
&self.reserved_account_keys.active
}
fn initialize_after_snapshot_restore<F, TP>(&mut self, rewards_thread_pool_builder: F)
where
F: FnOnce() -> TP,
TP: std::borrow::Borrow<ThreadPool>,
{
self.transaction_processor =
TransactionBatchProcessor::new_uninitialized(self.slot, self.epoch);
if let Some(compute_budget) = &self.compute_budget {
self.transaction_processor
.set_execution_cost(compute_budget.to_cost());
}
self.compute_and_apply_features_after_snapshot_restore();
self.recalculate_partitioned_rewards_if_active(rewards_thread_pool_builder);
self.transaction_processor
.fill_missing_sysvar_cache_entries(self);
}
fn compute_and_apply_genesis_features(&mut self) {
let feature_set = self.compute_active_feature_set(false).0;
self.feature_set = Arc::new(feature_set);
if self
.feature_set
.is_active(&feature_set::deprecate_rent_exemption_threshold::id())
{
self.rent_collector.deprecate_rent_exemption_threshold();
}
self.add_builtin_program_accounts();
self.apply_activated_features();
}
fn compute_and_apply_features_after_snapshot_restore(&mut self) {
let feature_set = self.compute_active_feature_set(false).0;
self.feature_set = Arc::new(feature_set);
self.apply_activated_features();
}
fn compute_and_apply_new_feature_activations(&mut self) {
let include_pending = true;
let (feature_set, new_feature_activations) =
self.compute_active_feature_set(include_pending);
self.feature_set = Arc::new(feature_set);
for feature_id in new_feature_activations.iter() {
if let Some(mut account) = self.get_account_with_fixed_root(feature_id) {
if let Some(mut feature) = feature::state::from_account(&account) {
feature.activated_at = Some(self.slot());
if feature::state::to_account(&feature, &mut account).is_some() {
self.store_account(feature_id, &account);
}
info!("Feature {} activated at slot {}", feature_id, self.slot());
}
}
}
self.reserved_account_keys = {
let mut reserved_keys = ReservedAccountKeys::clone(&self.reserved_account_keys);
reserved_keys.update_active_set(&self.feature_set);
Arc::new(reserved_keys)
};
if new_feature_activations.contains(&feature_set::deprecate_rent_exemption_threshold::id())
{
self.rent_collector.deprecate_rent_exemption_threshold();
self.update_rent();
}
let rent_feature_gates = [
(
feature_set::set_lamports_per_byte_to_6333::id(),
feature_set::set_lamports_per_byte_to_6333::LAMPORTS_PER_BYTE,
),
(
feature_set::set_lamports_per_byte_to_5080::id(),
feature_set::set_lamports_per_byte_to_5080::LAMPORTS_PER_BYTE,
),
(
feature_set::set_lamports_per_byte_to_2575::id(),
feature_set::set_lamports_per_byte_to_2575::LAMPORTS_PER_BYTE,
),
(
feature_set::set_lamports_per_byte_to_1322::id(),
feature_set::set_lamports_per_byte_to_1322::LAMPORTS_PER_BYTE,
),
(
feature_set::set_lamports_per_byte_to_696::id(),
feature_set::set_lamports_per_byte_to_696::LAMPORTS_PER_BYTE,
),
];
for (feature_id, lamports_per_byte_year) in rent_feature_gates {
if new_feature_activations.contains(&feature_id) {
self.rent_collector.rent.lamports_per_byte_year = lamports_per_byte_year;
self.update_rent();
}
}
if new_feature_activations.contains(&feature_set::pico_inflation::id()) {
*self.inflation.write().unwrap() = Inflation::pico();
self.fee_rate_governor.burn_percent = solana_fee_calculator::DEFAULT_BURN_PERCENT; self.rent_collector.rent.burn_percent = 50; }
if !new_feature_activations.is_disjoint(&self.feature_set.full_inflation_features_enabled())
{
*self.inflation.write().unwrap() = Inflation::full();
self.fee_rate_governor.burn_percent = solana_fee_calculator::DEFAULT_BURN_PERCENT; self.rent_collector.rent.burn_percent = 50; }
self.apply_new_builtin_program_feature_transitions(&new_feature_activations);
if new_feature_activations.contains(&feature_set::raise_block_limits_to_100m::id()) {
let block_cost_limit = simd_0286_block_limits();
let mut cost_tracker = self.write_cost_tracker().unwrap();
let account_cost_limit = cost_tracker.get_account_limit();
let vote_cost_limit = cost_tracker.get_vote_limit();
cost_tracker.set_limits(account_cost_limit, block_cost_limit, vote_cost_limit);
drop(cost_tracker);
if self
.feature_set
.is_active(&feature_set::raise_account_cu_limit::id())
{
self.apply_simd_0306_cost_tracker_changes();
}
}
if new_feature_activations.contains(&feature_set::raise_account_cu_limit::id()) {
self.apply_simd_0306_cost_tracker_changes();
}
if new_feature_activations.contains(&feature_set::vote_state_v4::id()) {
if let Err(e) = self.upgrade_core_bpf_program(
&solana_sdk_ids::stake::id(),
&feature_set::vote_state_v4::stake_program_buffer::id(),
"upgrade_stake_program_for_vote_state_v4",
) {
error!("Failed to upgrade Core BPF Stake program: {e}");
}
}
if new_feature_activations.contains(&feature_set::increase_cpi_account_info_limit::id()) {
self.apply_simd_0339_invoke_cost_changes();
}
if new_feature_activations.contains(&feature_set::replace_spl_token_with_p_token::id()) {
if let Err(e) = self.upgrade_loader_v2_program_with_loader_v3_program(
&feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID,
&feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER,
self.feature_set
.is_active(&relax_programdata_account_check_migration::id()),
"replace_spl_token_with_p_token",
) {
warn!(
"Failed to replace SPL Token with p-token buffer '{}': {e}",
feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER,
);
}
}
if new_feature_activations.contains(&feature_set::upgrade_bpf_stake_program_to_v5::id()) {
if let Err(e) = self.upgrade_core_bpf_program(
&solana_sdk_ids::stake::id(),
&feature_set::upgrade_bpf_stake_program_to_v5::buffer::id(),
"upgrade_stake_program_to_v5",
) {
error!("Failed to upgrade Core BPF Stake program: {e}");
}
}
}
fn apply_new_builtin_program_feature_transitions(
&mut self,
new_feature_activations: &AHashSet<Pubkey>,
) {
for builtin in BUILTINS.iter() {
if let Some(feature_id) = builtin.enable_feature_id {
if new_feature_activations.contains(&feature_id) {
self.add_builtin(
builtin.program_id,
builtin.name,
ProgramCacheEntry::new_builtin(
self.feature_set.activated_slot(&feature_id).unwrap_or(0),
builtin.name.len(),
builtin.entrypoint,
),
);
}
}
if let Some(core_bpf_migration_config) = &builtin.core_bpf_migration_config {
if new_feature_activations.contains(&core_bpf_migration_config.feature_id) {
if let Err(e) = self.migrate_builtin_to_core_bpf(
&builtin.program_id,
core_bpf_migration_config,
self.feature_set
.is_active(&relax_programdata_account_check_migration::id()),
) {
warn!(
"Failed to migrate builtin {} to Core BPF: {}",
builtin.name, e
);
}
}
};
}
for stateless_builtin in STATELESS_BUILTINS.iter() {
if let Some(core_bpf_migration_config) = &stateless_builtin.core_bpf_migration_config {
if new_feature_activations.contains(&core_bpf_migration_config.feature_id) {
if let Err(e) = self.migrate_builtin_to_core_bpf(
&stateless_builtin.program_id,
core_bpf_migration_config,
self.feature_set
.is_active(&relax_programdata_account_check_migration::id()),
) {
warn!(
"Failed to migrate stateless builtin {} to Core BPF: {}",
stateless_builtin.name, e
);
}
}
}
}
for precompile in get_precompiles() {
if let Some(feature_id) = &precompile.feature {
if new_feature_activations.contains(feature_id) {
self.add_precompile(&precompile.program_id);
}
}
}
}
fn adjust_sysvar_balance_for_rent(&self, account: &mut AccountSharedData) {
account.set_lamports(
self.get_minimum_balance_for_rent_exemption(account.data().len())
.max(account.lamports()),
);
}
fn compute_active_feature_set(&self, include_pending: bool) -> (FeatureSet, AHashSet<Pubkey>) {
let mut active = self.feature_set.active().clone();
let mut inactive = AHashSet::new();
let mut pending = AHashSet::new();
let slot = self.slot();
for feature_id in self.feature_set.inactive() {
let mut activated = None;
if let Some(account) = self.get_account_with_fixed_root(feature_id) {
if let Some(feature) = feature::state::from_account(&account) {
match feature.activated_at {
None if include_pending => {
pending.insert(*feature_id);
activated = Some(slot);
}
Some(activation_slot) if slot >= activation_slot => {
activated = Some(activation_slot);
}
_ => {}
}
}
}
if let Some(slot) = activated {
active.insert(*feature_id, slot);
} else {
inactive.insert(*feature_id);
}
}
(FeatureSet::new(active, inactive), pending)
}
pub fn compute_pending_activation_slot(&self, feature_id: &Pubkey) -> Option<Slot> {
let account = self.get_account_with_fixed_root(feature_id)?;
let feature = feature::from_account(&account)?;
if feature.activated_at.is_some() {
return None;
}
let active_epoch = self.epoch + 1;
Some(self.epoch_schedule.get_first_slot_in_epoch(active_epoch))
}
fn add_active_builtin_programs(&mut self) {
for builtin in BUILTINS.iter() {
let builtin_is_bpf = builtin.core_bpf_migration_config.is_some() && {
self.get_account(&builtin.program_id)
.map(|a| a.owner() == &bpf_loader_upgradeable::id())
.unwrap_or(false)
};
if builtin_is_bpf {
continue;
}
let builtin_is_active = builtin
.enable_feature_id
.map(|feature_id| self.feature_set.is_active(&feature_id))
.unwrap_or(true);
if builtin_is_active {
let activation_slot = builtin
.enable_feature_id
.and_then(|feature_id| self.feature_set.activated_slot(&feature_id))
.unwrap_or(0);
self.transaction_processor.add_builtin(
builtin.program_id,
ProgramCacheEntry::new_builtin(
activation_slot,
builtin.name.len(),
builtin.entrypoint,
),
);
}
}
}
fn add_builtin_program_accounts(&mut self) {
for builtin in BUILTINS.iter() {
let builtin_is_bpf = builtin.core_bpf_migration_config.is_some() && {
self.get_account(&builtin.program_id)
.map(|a| a.owner() == &bpf_loader_upgradeable::id())
.unwrap_or(false)
};
if builtin_is_bpf {
continue;
}
let builtin_is_active = builtin
.enable_feature_id
.map(|feature_id| self.feature_set.is_active(&feature_id))
.unwrap_or(true);
if builtin_is_active {
self.add_builtin_account(builtin.name, &builtin.program_id);
}
}
for precompile in get_precompiles() {
let precompile_is_active = precompile
.feature
.as_ref()
.map(|feature_id| self.feature_set.is_active(feature_id))
.unwrap_or(true);
if precompile_is_active {
self.add_precompile(&precompile.program_id);
}
}
}
pub fn calculate_accounts_data_size(&self) -> ScanResult<u64> {
let accounts = self.get_all_accounts(false)?;
let accounts_data_size = accounts
.into_iter()
.map(|(_pubkey, account, _slot)| account.data().len() as u64)
.try_fold(0, u64::checked_add)
.expect("accounts data size cannot overflow");
Ok(accounts_data_size)
}
pub fn is_in_slot_hashes_history(&self, slot: &Slot) -> bool {
if slot < &self.slot {
if let Ok(slot_hashes) = self.transaction_processor.sysvar_cache().get_slot_hashes() {
return slot_hashes.get(slot).is_some();
}
}
false
}
pub fn check_program_deployment_slot(&self) -> bool {
self.check_program_deployment_slot
}
pub fn set_check_program_deployment_slot(&mut self, check: bool) {
self.check_program_deployment_slot = check;
}
pub fn fee_structure(&self) -> &FeeStructure {
&self.fee_structure
}
pub fn parent_block_id(&self) -> Option<Hash> {
self.parent().and_then(|p| p.block_id())
}
pub fn block_id(&self) -> Option<Hash> {
*self.block_id.read().unwrap()
}
pub fn set_block_id(&self, block_id: Option<Hash>) {
*self.block_id.write().unwrap() = block_id;
}
pub fn compute_budget(&self) -> Option<ComputeBudget> {
self.compute_budget
}
pub fn add_builtin(&self, program_id: Pubkey, name: &str, builtin: ProgramCacheEntry) {
debug!("Adding program {name} under {program_id:?}");
self.add_builtin_account(name, &program_id);
self.transaction_processor.add_builtin(program_id, builtin);
debug!("Added program {name} under {program_id:?}");
}
fn add_builtin_account(&self, name: &str, program_id: &Pubkey) {
let existing_genuine_program =
self.get_account_with_fixed_root(program_id)
.and_then(|account| {
if native_loader::check_id(account.owner()) {
Some(account)
} else {
self.burn_and_purge_account(program_id, account);
None
}
});
if existing_genuine_program.is_some() {
return;
}
assert!(
!self.freeze_started(),
"Can't change frozen bank by adding not-existing new builtin program ({name}, \
{program_id}). Maybe, inconsistent program activation is detected on snapshot \
restore?"
);
let (lamports, rent_epoch) =
self.inherit_specially_retained_account_fields(&existing_genuine_program);
let account: AccountSharedData = AccountSharedData::from(Account {
lamports,
data: name.as_bytes().to_vec(),
owner: solana_sdk_ids::native_loader::id(),
executable: true,
rent_epoch,
});
self.store_account_and_update_capitalization(program_id, &account);
}
pub fn get_bank_hash_stats(&self) -> BankHashStats {
self.bank_hash_stats.load()
}
pub fn clear_epoch_rewards_cache(&self) {
self.epoch_rewards_calculation_cache.lock().unwrap().clear();
}
pub fn set_accounts_lt_hash_for_snapshot_minimizer(&self, accounts_lt_hash: AccountsLtHash) {
*self.accounts_lt_hash.lock().unwrap() = accounts_lt_hash;
}
pub fn get_collector_fee_details(&self) -> CollectorFeeDetails {
self.collector_fee_details.read().unwrap().clone()
}
fn maybe_filter_vote_accounts_for_vat(&self, vote_accounts: &VoteAccounts) -> VoteAccounts {
if self
.feature_set
.is_active(&agave_feature_set::validator_admission_ticket::id())
{
let vote_account_rent_exempt_minimum = self
.rent_collector
.rent
.minimum_balance(VoteStateV4::size_of());
let minimum_vote_account_balance = if self
.feature_set
.is_active(&agave_feature_set::alpenglow::id())
{
vote_account_rent_exempt_minimum + VAT_TO_BURN_PER_EPOCH
} else {
vote_account_rent_exempt_minimum
};
vote_accounts
.clone_and_filter_for_vat(MAX_ALPENGLOW_VOTE_ACCOUNTS, minimum_vote_account_balance)
} else {
vote_accounts.clone()
}
}
pub fn get_top_epoch_stakes(&self) -> Stakes<StakeAccount<Delegation>> {
let vote_account_rent_exempt_minimum = self
.rent_collector
.rent
.minimum_balance(VoteStateV4::size_of());
let minimum_vote_account_balance =
if self.feature_set.is_active(&feature_set::alpenglow::id()) {
vote_account_rent_exempt_minimum + VAT_TO_BURN_PER_EPOCH
} else {
vote_account_rent_exempt_minimum
};
if self
.feature_set
.is_active(&feature_set::validator_admission_ticket::id())
{
self.stakes_cache
.stakes()
.clone_and_filter_for_vat(MAX_ALPENGLOW_VOTE_ACCOUNTS, minimum_vote_account_balance)
} else {
self.stakes_cache.stakes().clone()
}
}
}
impl InvokeContextCallback for Bank {
fn get_epoch_stake(&self) -> u64 {
self.get_current_epoch_total_stake()
}
fn get_epoch_stake_for_vote_account(&self, vote_address: &Pubkey) -> u64 {
self.get_current_epoch_vote_accounts()
.get(vote_address)
.map(|(stake, _)| *stake)
.unwrap_or(0)
}
fn is_precompile(&self, program_id: &Pubkey) -> bool {
is_precompile(program_id, |feature_id: &Pubkey| {
self.feature_set.is_active(feature_id)
})
}
fn process_precompile(
&self,
program_id: &Pubkey,
data: &[u8],
instruction_datas: Vec<&[u8]>,
) -> std::result::Result<(), PrecompileError> {
if let Some(precompile) = get_precompile(program_id, |feature_id: &Pubkey| {
self.feature_set.is_active(feature_id)
}) {
precompile.verify(data, &instruction_datas, &self.feature_set)
} else {
Err(PrecompileError::InvalidPublicKey)
}
}
}
impl TransactionProcessingCallback for Bank {
fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<(AccountSharedData, Slot)> {
self.rc
.accounts
.load_with_fixed_root(&self.ancestors, pubkey)
}
fn inspect_account(&self, address: &Pubkey, account_state: AccountState, is_writable: bool) {
self.inspect_account_for_accounts_lt_hash(address, &account_state, is_writable);
}
}
impl fmt::Debug for Bank {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bank")
.field("slot", &self.slot)
.field("bank_id", &self.bank_id)
.field("block_height", &self.block_height)
.field("parent_slot", &self.parent_slot)
.field("capitalization", &self.capitalization())
.finish_non_exhaustive()
}
}
#[cfg(feature = "dev-context-only-utils")]
impl Bank {
pub fn wrap_with_bank_forks_for_tests(self) -> (Arc<Self>, Arc<RwLock<BankForks>>) {
let bank_forks = BankForks::new_rw_arc(self);
let bank = bank_forks.read().unwrap().root_bank();
(bank, bank_forks)
}
pub fn default_for_tests() -> Self {
let accounts_db = AccountsDb::default_for_tests();
let accounts = Accounts::new(Arc::new(accounts_db));
Self::default_with_accounts(accounts)
}
pub fn new_with_bank_forks_for_tests(
genesis_config: &GenesisConfig,
) -> (Arc<Self>, Arc<RwLock<BankForks>>) {
let bank = Self::new_for_tests(genesis_config);
bank.wrap_with_bank_forks_for_tests()
}
pub fn new_for_tests(genesis_config: &GenesisConfig) -> Self {
Self::new_with_config_for_tests(genesis_config, BankTestConfig::default())
}
pub fn new_with_mockup_builtin_for_tests(
genesis_config: &GenesisConfig,
program_id: Pubkey,
builtin_function: BuiltinFunctionWithContext,
) -> (Arc<Self>, Arc<RwLock<BankForks>>) {
let mut bank = Self::new_for_tests(genesis_config);
bank.add_mockup_builtin(program_id, builtin_function);
bank.wrap_with_bank_forks_for_tests()
}
pub fn new_with_config_for_tests(
genesis_config: &GenesisConfig,
test_config: BankTestConfig,
) -> Self {
Self::new_with_paths_for_tests(
genesis_config,
Arc::new(RuntimeConfig::default()),
test_config,
Vec::new(),
)
}
pub fn new_with_paths_for_tests(
genesis_config: &GenesisConfig,
runtime_config: Arc<RuntimeConfig>,
test_config: BankTestConfig,
paths: Vec<PathBuf>,
) -> Self {
Self::new_from_genesis(
genesis_config,
runtime_config,
paths,
None,
test_config.accounts_db_config,
None,
Some(Pubkey::new_unique()),
Arc::default(),
None,
None,
)
}
pub fn new_for_benches(genesis_config: &GenesisConfig) -> Self {
Self::new_with_paths_for_benches(genesis_config, Vec::new())
}
pub fn new_with_paths_for_benches(genesis_config: &GenesisConfig, paths: Vec<PathBuf>) -> Self {
Self::new_from_genesis(
genesis_config,
Arc::<RuntimeConfig>::default(),
paths,
None,
ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS,
None,
Some(Pubkey::new_unique()),
Arc::default(),
None,
None,
)
}
pub fn new_from_parent_with_bank_forks(
bank_forks: &RwLock<BankForks>,
parent: Arc<Bank>,
leader_id: &Pubkey,
slot: Slot,
) -> Arc<Self> {
let bank = Bank::new_from_parent(parent, leader_id, slot);
bank_forks
.write()
.unwrap()
.insert(bank)
.clone_without_scheduler()
}
pub fn prepare_batch_for_tests(
&self,
txs: Vec<Transaction>,
) -> TransactionBatch<'_, '_, RuntimeTransaction<SanitizedTransaction>> {
let sanitized_txs = txs
.into_iter()
.map(RuntimeTransaction::from_transaction_for_tests)
.collect::<Vec<_>>();
TransactionBatch::new(
self.try_lock_accounts(&sanitized_txs),
self,
OwnedOrBorrowed::Owned(sanitized_txs),
)
}
pub fn set_accounts_data_size_initial_for_tests(&mut self, amount: u64) {
self.accounts_data_size_initial = amount;
}
pub fn update_accounts_data_size_delta_off_chain_for_tests(&self, amount: i64) {
self.update_accounts_data_size_delta_off_chain(amount)
}
#[must_use]
pub fn process_transactions<'a>(
&self,
txs: impl Iterator<Item = &'a Transaction>,
) -> Vec<Result<()>> {
self.try_process_transactions(txs).unwrap()
}
#[must_use]
pub fn process_entry_transactions(&self, txs: Vec<VersionedTransaction>) -> Vec<Result<()>> {
self.try_process_entry_transactions(txs).unwrap()
}
pub fn get_sysvar_cache_for_tests(&self) -> SysvarCache {
self.transaction_processor.get_sysvar_cache_for_tests()
}
pub fn calculate_accounts_lt_hash_for_tests(&self) -> AccountsLtHash {
self.rc
.accounts
.accounts_db
.calculate_accounts_lt_hash_at_startup_from_index(&self.ancestors, self.slot)
}
pub fn get_transaction_processor(&self) -> &TransactionBatchProcessor<BankForks> {
&self.transaction_processor
}
pub fn set_fee_structure(&mut self, fee_structure: &FeeStructure) {
self.fee_structure = fee_structure.clone();
}
pub fn load_program(
&self,
pubkey: &Pubkey,
effective_epoch: Epoch,
) -> Option<Arc<ProgramCacheEntry>> {
let environments = self
.transaction_processor
.get_environments_for_epoch(effective_epoch);
load_program_with_pubkey(
self,
&environments,
pubkey,
self.slot(),
&mut ExecuteTimings::default(), )
.map(|(loaded_program, _last_modification_slot)| loaded_program)
}
pub fn withdraw(&self, pubkey: &Pubkey, lamports: u64) -> Result<()> {
match self.get_account_with_fixed_root(pubkey) {
Some(mut account) => {
let min_balance = match get_system_account_kind(&account) {
Some(SystemAccountKind::Nonce) => self
.rent_collector
.rent
.minimum_balance(nonce::state::State::size()),
_ => 0,
};
lamports
.checked_add(min_balance)
.filter(|required_balance| *required_balance <= account.lamports())
.ok_or(TransactionError::InsufficientFundsForFee)?;
account
.checked_sub_lamports(lamports)
.map_err(|_| TransactionError::InsufficientFundsForFee)?;
self.store_account(pubkey, &account);
Ok(())
}
None => Err(TransactionError::AccountNotFound),
}
}
pub fn set_hash_overrides(&self, hash_overrides: HashOverrides) {
*self.hash_overrides.lock().unwrap() = hash_overrides;
}
pub(crate) fn get_stake_accounts(&self, minimized_account_set: &DashSet<Pubkey>) {
self.stakes_cache
.stakes()
.stake_delegations()
.iter()
.for_each(|(pubkey, _)| {
minimized_account_set.insert(*pubkey);
});
self.stakes_cache
.stakes()
.staked_nodes()
.par_iter()
.for_each(|(pubkey, _)| {
minimized_account_set.insert(*pubkey);
});
}
}
fn calculate_data_size_delta(old_data_size: usize, new_data_size: usize) -> i64 {
assert!(old_data_size <= i64::MAX as usize);
assert!(new_data_size <= i64::MAX as usize);
let old_data_size = old_data_size as i64;
let new_data_size = new_data_size as i64;
new_data_size.saturating_sub(old_data_size)
}
impl Drop for Bank {
fn drop(&mut self) {
if let Some(drop_callback) = self.drop_callback.read().unwrap().0.as_ref() {
drop_callback.callback(self);
} else {
self.rc
.accounts
.accounts_db
.purge_slot(self.slot(), self.bank_id(), false);
}
}
}
pub mod test_utils {
use {
super::Bank,
crate::installed_scheduler_pool::BankWithScheduler,
solana_account::{ReadableAccount, WritableAccount, state_traits::StateMut},
solana_instruction::error::LamportsError,
solana_pubkey::Pubkey,
solana_sha256_hasher::hashv,
solana_vote_interface::state::VoteStateV4,
solana_vote_program::vote_state::{BlockTimestamp, VoteStateVersions},
std::sync::Arc,
};
pub fn goto_end_of_slot(bank: Arc<Bank>) {
goto_end_of_slot_with_scheduler(&BankWithScheduler::new_without_scheduler(bank))
}
pub fn goto_end_of_slot_with_scheduler(bank: &BankWithScheduler) {
let mut tick_hash = bank.last_blockhash();
loop {
tick_hash = hashv(&[tick_hash.as_ref(), &[42]]);
bank.register_tick(&tick_hash);
if tick_hash == bank.last_blockhash() {
bank.freeze();
return;
}
}
}
pub fn update_vote_account_timestamp(
timestamp: BlockTimestamp,
bank: &Bank,
vote_pubkey: &Pubkey,
) {
let mut vote_account = bank.get_account(vote_pubkey).unwrap_or_default();
let mut vote_state = VoteStateV4::deserialize(vote_account.data(), vote_pubkey)
.ok()
.unwrap_or_default();
vote_state.last_timestamp = timestamp;
let versioned = VoteStateVersions::new_v4(vote_state);
vote_account.set_state(&versioned).unwrap();
bank.store_account(vote_pubkey, &vote_account);
}
pub fn deposit(
bank: &Bank,
pubkey: &Pubkey,
lamports: u64,
) -> std::result::Result<u64, LamportsError> {
let mut account = bank
.get_account_with_fixed_root_no_cache(pubkey)
.unwrap_or_default();
account.checked_add_lamports(lamports)?;
bank.store_account(pubkey, &account);
Ok(account.lamports())
}
}