From 3326db8b737731da3afb1e4e7e603fe2f456403a Mon Sep 17 00:00:00 2001 From: Joe C Date: Fri, 29 Mar 2024 16:45:41 -0500 Subject: [PATCH 1/4] sdk: add feature gate for sol-get-epoch-stake --- sdk/src/feature_set.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 9fff562fcf3d81..221bf3f2b34ccf 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -801,6 +801,10 @@ pub mod abort_on_invalid_curve { solana_sdk::declare_id!("FuS3FPfJDKSNot99ECLXtp3rueq36hMNStJkPJwWodLh"); } +pub mod enable_syscall_get_epoch_stake { + solana_sdk::declare_id!("7mScTYkJXsbdrcwTQRs7oeCSXoJm4WjzBsRyf8bCU3Np"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -995,7 +999,8 @@ lazy_static! { (enable_tower_sync_ix::id(), "Enable tower sync vote instruction"), (chained_merkle_conflict_duplicate_proofs::id(), "generate duplicate proofs for chained merkle root conflicts"), (reward_full_priority_fee::id(), "Reward full priority fee to validators #34731"), - (abort_on_invalid_curve::id(), "Abort when elliptic curve syscalls invoked on invalid curve id SIMD-0137") + (abort_on_invalid_curve::id(), "Abort when elliptic curve syscalls invoked on invalid curve id SIMD-0137"), + (enable_syscall_get_epoch_stake::id(), "Enable syscall: sol_get_epoch_stake #884") /*************** ADD NEW FEATURES HERE ***************/ ] .iter() From b4a053d8395949f35db55e8cf2c6c295fe87d642 Mon Sep 17 00:00:00 2001 From: Joe C Date: Fri, 29 Mar 2024 16:54:47 -0500 Subject: [PATCH 2/4] svm: add callback for epoch stake --- runtime/src/bank.rs | 4 ++++ svm/src/account_loader.rs | 7 +++++++ svm/src/program_loader.rs | 5 +++++ svm/src/transaction_processing_callback.rs | 2 ++ svm/src/transaction_processor.rs | 5 +++++ svm/tests/mock_bank.rs | 5 +++++ 6 files changed, 28 insertions(+) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index a278750f300463..ff2a0974984644 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -6804,6 +6804,10 @@ impl TransactionProcessingCallback for Bank { self.feature_set.clone() } + fn get_epoch_stake(&self, vote_address: &Pubkey) -> u64 { + self.epoch_vote_account_stake(vote_address) + } + fn get_program_match_criteria(&self, program: &Pubkey) -> ProgramCacheMatchCriteria { if self.check_program_modification_slot { self.program_modification_slot(program) diff --git a/svm/src/account_loader.rs b/svm/src/account_loader.rs index 6190fafa36c6b2..16bcae9028b99a 100644 --- a/svm/src/account_loader.rs +++ b/svm/src/account_loader.rs @@ -499,6 +499,7 @@ mod tests { accounts_map: HashMap, rent_collector: RentCollector, feature_set: Arc, + epoch_stake: u64, } impl TransactionProcessingCallback for TestCallbacks { @@ -521,6 +522,10 @@ mod tests { fn get_feature_set(&self) -> Arc { self.feature_set.clone() } + + fn get_epoch_stake(&self, _vote_address: &Pubkey) -> u64 { + self.epoch_stake + } } fn load_accounts_with_fee_and_rent( @@ -542,6 +547,7 @@ mod tests { accounts_map, rent_collector: rent_collector.clone(), feature_set: Arc::new(feature_set.clone()), + epoch_stake: 0, }; load_accounts( &callbacks, @@ -1030,6 +1036,7 @@ mod tests { accounts_map, rent_collector: RentCollector::default(), feature_set: Arc::new(FeatureSet::all_enabled()), + epoch_stake: 0, }; load_accounts( &callbacks, diff --git a/svm/src/program_loader.rs b/svm/src/program_loader.rs index 439ac3da55c6ab..00f1f0ad9cd106 100644 --- a/svm/src/program_loader.rs +++ b/svm/src/program_loader.rs @@ -270,6 +270,7 @@ mod tests { pub struct MockBankCallback { rent_collector: RentCollector, feature_set: Arc, + epoch_stake: u64, pub account_shared_data: RefCell>, } @@ -302,6 +303,10 @@ mod tests { self.feature_set.clone() } + fn get_epoch_stake(&self, _vote_address: &Pubkey) -> u64 { + self.epoch_stake + } + fn add_builtin_account(&self, name: &str, program_id: &Pubkey) { let mut account_data = AccountSharedData::default(); account_data.set_data(name.as_bytes().to_vec()); diff --git a/svm/src/transaction_processing_callback.rs b/svm/src/transaction_processing_callback.rs index ded481eda8ea5f..b336b6fb080f7e 100644 --- a/svm/src/transaction_processing_callback.rs +++ b/svm/src/transaction_processing_callback.rs @@ -19,6 +19,8 @@ pub trait TransactionProcessingCallback { fn get_feature_set(&self) -> Arc; + fn get_epoch_stake(&self, vote_address: &Pubkey) -> u64; + fn get_program_match_criteria(&self, _program: &Pubkey) -> ProgramCacheMatchCriteria { ProgramCacheMatchCriteria::NoCriteria } diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index 4fb54e5a1f1713..5ba154c405c183 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -783,6 +783,7 @@ mod tests { pub struct MockBankCallback { rent_collector: RentCollector, feature_set: Arc, + epoch_stake: u64, pub account_shared_data: RefCell>, } @@ -815,6 +816,10 @@ mod tests { self.feature_set.clone() } + fn get_epoch_stake(&self, _vote_address: &Pubkey) -> u64 { + self.epoch_stake + } + fn add_builtin_account(&self, name: &str, program_id: &Pubkey) { let mut account_data = AccountSharedData::default(); account_data.set_data(name.as_bytes().to_vec()); diff --git a/svm/tests/mock_bank.rs b/svm/tests/mock_bank.rs index 38ae43f7259062..a189e3dc38cc69 100644 --- a/svm/tests/mock_bank.rs +++ b/svm/tests/mock_bank.rs @@ -15,6 +15,7 @@ use { pub struct MockBankCallback { rent_collector: RentCollector, feature_set: Arc, + epoch_stake: u64, pub account_shared_data: RefCell>, } @@ -48,6 +49,10 @@ impl TransactionProcessingCallback for MockBankCallback { self.feature_set.clone() } + fn get_epoch_stake(&self, _vote_address: &Pubkey) -> u64 { + self.epoch_stake + } + fn add_builtin_account(&self, name: &str, program_id: &Pubkey) { let account_data = native_loader::create_loadable_account_with_fields(name, (5000, 0)); From 20c01c2bfe7b067d796d08e84ab65402f7099add Mon Sep 17 00:00:00 2001 From: Joe C Date: Fri, 29 Mar 2024 16:51:11 -0500 Subject: [PATCH 3/4] invoke context: add epoch stake --- program-runtime/src/invoke_context.rs | 4 ++++ runtime/src/bank/builtins/core_bpf_migration/mod.rs | 1 + svm/src/message_processor.rs | 7 +++++++ svm/src/transaction_processor.rs | 2 ++ 4 files changed, 14 insertions(+) diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index af5e0c688411b6..49cd757daf27c8 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -169,6 +169,7 @@ pub struct InvokeContext<'a> { pub programs_loaded_for_tx_batch: &'a ProgramCacheForTxBatch, pub programs_modified_by_tx: &'a mut ProgramCacheForTxBatch, pub feature_set: Arc, + pub get_epoch_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64, pub timings: ExecuteDetailsTimings, pub blockhash: Hash, pub lamports_per_signature: u64, @@ -186,6 +187,7 @@ impl<'a> InvokeContext<'a> { programs_loaded_for_tx_batch: &'a ProgramCacheForTxBatch, programs_modified_by_tx: &'a mut ProgramCacheForTxBatch, feature_set: Arc, + get_epoch_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64, blockhash: Hash, lamports_per_signature: u64, ) -> Self { @@ -199,6 +201,7 @@ impl<'a> InvokeContext<'a> { programs_loaded_for_tx_batch, programs_modified_by_tx, feature_set, + get_epoch_stake_callback, timings: ExecuteDetailsTimings::default(), blockhash, lamports_per_signature, @@ -685,6 +688,7 @@ macro_rules! with_mock_invoke_context { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); diff --git a/runtime/src/bank/builtins/core_bpf_migration/mod.rs b/runtime/src/bank/builtins/core_bpf_migration/mod.rs index f1fc3a85981271..b40df7aa973b67 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/mod.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/mod.rs @@ -180,6 +180,7 @@ impl Bank { &programs_loaded, &mut programs_modified, self.feature_set.clone(), + &|_| 0, Hash::default(), 0, ); diff --git a/svm/src/message_processor.rs b/svm/src/message_processor.rs index 15de67d20486bb..65f2de59a2f672 100644 --- a/svm/src/message_processor.rs +++ b/svm/src/message_processor.rs @@ -280,6 +280,7 @@ mod tests { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); @@ -331,6 +332,7 @@ mod tests { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); @@ -372,6 +374,7 @@ mod tests { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); @@ -504,6 +507,7 @@ mod tests { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); @@ -540,6 +544,7 @@ mod tests { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); @@ -573,6 +578,7 @@ mod tests { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); @@ -667,6 +673,7 @@ mod tests { &programs_loaded_for_tx_batch, &mut programs_modified_by_tx, Arc::new(FeatureSet::all_enabled()), + &|_| 0, Hash::default(), 0, ); diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index 5ba154c405c183..0d4c00ab28100b 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -531,6 +531,7 @@ impl TransactionBatchProcessor { programs_loaded_for_tx_batch.latest_root_epoch, ); let sysvar_cache = &self.sysvar_cache.read().unwrap(); + let epoch_stake_callback = |pubkey| callback.get_epoch_stake(pubkey); let mut invoke_context = InvokeContext::new( &mut transaction_context, @@ -540,6 +541,7 @@ impl TransactionBatchProcessor { programs_loaded_for_tx_batch, &mut programs_modified_by_tx, callback.get_feature_set(), + &epoch_stake_callback, blockhash, lamports_per_signature, ); From 66d1534c8457b62e646df6ecf0bc4b83a0ac1e55 Mon Sep 17 00:00:00 2001 From: Joe C Date: Tue, 30 Apr 2024 19:14:27 -0500 Subject: [PATCH 4/4] syscall: get-epoch-stake --- programs/bpf_loader/src/syscalls/mod.rs | 46 +++++++++++++++++++++++-- sdk/program/src/epoch_stake.rs | 21 +++++++++++ sdk/program/src/lib.rs | 1 + sdk/program/src/program_stubs.rs | 10 ++++++ sdk/program/src/syscalls/definitions.rs | 1 + 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 sdk/program/src/epoch_stake.rs diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index ab2e85e34906b0..eb07b7e2e113fb 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -38,8 +38,9 @@ use { disable_deploy_of_alloc_free_syscall, disable_fees_sysvar, enable_alt_bn128_compression_syscall, enable_alt_bn128_syscall, enable_big_mod_exp_syscall, enable_partitioned_epoch_reward, enable_poseidon_syscall, - error_on_syscall_bpf_function_hash_collisions, last_restart_slot_sysvar, - reject_callx_r10, remaining_compute_units_syscall_enabled, switch_to_new_elf_parser, + enable_syscall_get_epoch_stake, error_on_syscall_bpf_function_hash_collisions, + last_restart_slot_sysvar, reject_callx_r10, remaining_compute_units_syscall_enabled, + switch_to_new_elf_parser, }, hash::{Hash, Hasher}, instruction::{AccountMeta, InstructionError, ProcessedSiblingInstruction}, @@ -278,6 +279,8 @@ pub fn create_program_runtime_environment_v1<'a>( let enable_poseidon_syscall = feature_set.is_active(&enable_poseidon_syscall::id()); let remaining_compute_units_syscall_enabled = feature_set.is_active(&remaining_compute_units_syscall_enabled::id()); + let enable_syscall_get_epoch_stake = + feature_set.is_active(&enable_syscall_get_epoch_stake::id()); // !!! ATTENTION !!! // When adding new features for RBPF here, // also add them to `Bank::apply_builtin_program_feature_transitions()`. @@ -464,6 +467,14 @@ pub fn create_program_runtime_environment_v1<'a>( SyscallAltBn128Compression::vm, )?; + // Get Epoch Stake + register_feature_gated_function!( + result, + enable_syscall_get_epoch_stake, + *b"sol_syscall_get_epoch_stake", + SyscallGetEpochStake::vm, + )?; + // Log data result.register_function_hashed(*b"sol_log_data", SyscallLogData::vm)?; @@ -1997,6 +2008,37 @@ declare_builtin_function!( } ); +declare_builtin_function!( + // Get Epoch Stake Syscall + SyscallGetEpochStake, + fn rust( + invoke_context: &mut InvokeContext, + var_addr: u64, + vote_address: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &mut MemoryMapping, + ) -> Result { + let compute_budget = invoke_context.get_compute_budget(); + let div_by_cpi = |n: u64| -> u64 { + n.checked_div(compute_budget.cpi_bytes_per_unit) + .unwrap_or(u64::MAX) + }; + consume_compute_meter(invoke_context, div_by_cpi(32).saturating_add(div_by_cpi(8)))?; + + let check_aligned = invoke_context.get_check_aligned(); + + let vote_address = translate_type::(memory_mapping, vote_address, check_aligned)?; + + let var = translate_type_mut::(memory_mapping, var_addr, check_aligned)?; + + *var = (invoke_context.get_epoch_stake_callback)(vote_address); + + Ok(0) + } +); + #[cfg(test)] #[allow(clippy::arithmetic_side_effects)] #[allow(clippy::indexing_slicing)] diff --git a/sdk/program/src/epoch_stake.rs b/sdk/program/src/epoch_stake.rs new file mode 100644 index 00000000000000..bf10c97977275e --- /dev/null +++ b/sdk/program/src/epoch_stake.rs @@ -0,0 +1,21 @@ +use crate::{program_error::ProgramError, pubkey::Pubkey}; + +/// Get the current epoch stake for a given vote account. +/// Returns `0` for any provided pubkey that isn't a vote account. +pub fn get_epoch_stake(vote_address: &Pubkey) -> Result { + let mut var = 0u64; + let var_addr = &mut var as *mut _ as *mut u8; + + let vote_address = vote_address as *const _ as *const u8; + + #[cfg(target_os = "solana")] + let result = unsafe { crate::syscalls::sol_syscall_get_epoch_stake(var_addr, vote_address) }; + + #[cfg(not(target_os = "solana"))] + let result = crate::program_stubs::sol_syscall_get_epoch_stake(var_addr, vote_address); + + match result { + crate::entrypoint::SUCCESS => Ok(var), + e => Err(e.into()), + } +} diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 1f464337028287..b5a3840f84de28 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -491,6 +491,7 @@ pub mod entrypoint; pub mod entrypoint_deprecated; pub mod epoch_rewards; pub mod epoch_schedule; +pub mod epoch_stake; pub mod feature; pub mod fee_calculator; pub mod hash; diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index cf890659fa68a1..14d4ebd356caf5 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -61,6 +61,9 @@ pub trait SyscallStubs: Sync + Send { fn sol_get_last_restart_slot(&self, _var_addr: *mut u8) -> u64 { UNSUPPORTED_SYSVAR } + fn sol_syscall_get_epoch_stake(&self, _var_addr: *mut u8, _vote_address: *const u8) -> u64 { + UNSUPPORTED_SYSVAR + } /// # Safety unsafe fn sol_memcpy(&self, dst: *mut u8, src: *const u8, n: usize) { // cannot be overlapping @@ -171,6 +174,13 @@ pub(crate) fn sol_get_last_restart_slot(var_addr: *mut u8) -> u64 { .sol_get_last_restart_slot(var_addr) } +pub(crate) fn sol_syscall_get_epoch_stake(var_addr: *mut u8, vote_address: *const u8) -> u64 { + SYSCALL_STUBS + .read() + .unwrap() + .sol_syscall_get_epoch_stake(var_addr, vote_address) +} + pub(crate) fn sol_memcpy(dst: *mut u8, src: *const u8, n: usize) { unsafe { SYSCALL_STUBS.read().unwrap().sol_memcpy(dst, src, n); diff --git a/sdk/program/src/syscalls/definitions.rs b/sdk/program/src/syscalls/definitions.rs index b2dedceba953a0..534b53882af217 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -72,6 +72,7 @@ define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64); define_syscall!(fn sol_poseidon(parameters: u64, endianness: u64, vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); define_syscall!(fn sol_remaining_compute_units() -> u64); define_syscall!(fn sol_alt_bn128_compression(op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64); +define_syscall!(fn sol_syscall_get_epoch_stake(addr: *mut u8, vote_address: *const u8) -> u64); #[cfg(target_feature = "static-syscalls")] pub const fn sys_hash(name: &str) -> usize {