diff --git a/account-decoder/src/parse_sysvar.rs b/account-decoder/src/parse_sysvar.rs index 7b7c0821b9..7f7469490f 100644 --- a/account-decoder/src/parse_sysvar.rs +++ b/account-decoder/src/parse_sysvar.rs @@ -15,7 +15,7 @@ use { slot_hashes::SlotHashes, slot_history::{self, SlotHistory}, stake_history::{StakeHistory, StakeHistoryEntry}, - sysvar::{self, rewards::Rewards}, + sysvar::{self, last_restart_slot::LastRestartSlot, rewards::Rewards}, }, }; @@ -82,6 +82,13 @@ pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result(data) + .ok() + .map(|last_restart_slot| { + let last_restart_slot = last_restart_slot.last_restart_slot; + SysvarAccountType::LastRestartSlot(UiLastRestartSlot { last_restart_slot }) + }) } else { None } @@ -105,6 +112,7 @@ pub enum SysvarAccountType { SlotHashes(Vec), SlotHistory(UiSlotHistory), StakeHistory(Vec), + LastRestartSlot(UiLastRestartSlot), } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)] @@ -218,6 +226,12 @@ pub struct UiStakeHistoryEntry { pub stake_history: StakeHistoryEntry, } +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)] +#[serde(rename_all = "camelCase")] +pub struct UiLastRestartSlot { + pub last_restart_slot: Slot, +} + #[cfg(test)] mod test { #[allow(deprecated)] @@ -334,5 +348,20 @@ mod test { let bad_data = vec![0; 4]; assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err()); + + let last_restart_slot = LastRestartSlot { + last_restart_slot: 1282, + }; + let last_restart_slot_account = create_account_for_test(&last_restart_slot); + assert_eq!( + parse_sysvar( + &last_restart_slot_account.data, + &sysvar::last_restart_slot::id() + ) + .unwrap(), + SysvarAccountType::LastRestartSlot(UiLastRestartSlot { + last_restart_slot: 1282 + }) + ); } } diff --git a/docs/src/developing/runtime-facilities/sysvars.md b/docs/src/developing/runtime-facilities/sysvars.md index 97927a2e92..908ecdafe5 100644 --- a/docs/src/developing/runtime-facilities/sysvars.md +++ b/docs/src/developing/runtime-facilities/sysvars.md @@ -155,4 +155,12 @@ determining whether epoch rewards distribution has finished. - Address: `SysvarEpochRewards1111111111111111111111111` - Layout: - [EpochRewards](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/epoch_rewards/struct.EpochRewards.html) \ No newline at end of file + [EpochRewards](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/epoch_rewards/struct.EpochRewards.html) + +## LastRestartSlot + +The LastRestartSlot sysvar contains the slot number of the last restart or _0_ (zero) if none ever happened. + +- Address: `SysvarLastRestartS1ot1111111111111111111111` +- Layout: + [LastRestartSlot](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/last_restart_slot/struct.LastRestartSlot.html) diff --git a/program-runtime/src/sysvar_cache.rs b/program-runtime/src/sysvar_cache.rs index 4557a5b9f4..58bd8674ef 100644 --- a/program-runtime/src/sysvar_cache.rs +++ b/program-runtime/src/sysvar_cache.rs @@ -1,5 +1,7 @@ #[allow(deprecated)] -use solana_sdk::sysvar::{fees::Fees, recent_blockhashes::RecentBlockhashes}; +use solana_sdk::sysvar::{ + fees::Fees, last_restart_slot::LastRestartSlot, recent_blockhashes::RecentBlockhashes, +}; use { crate::invoke_context::InvokeContext, solana_sdk::{ @@ -33,6 +35,7 @@ pub struct SysvarCache { #[allow(deprecated)] recent_blockhashes: Option>, stake_history: Option>, + last_restart_slot: Option>, } impl SysvarCache { @@ -76,6 +79,16 @@ impl SysvarCache { self.rent = Some(Arc::new(rent)); } + pub fn get_last_restart_slot(&self) -> Result, InstructionError> { + self.last_restart_slot + .clone() + .ok_or(InstructionError::UnsupportedSysvar) + } + + pub fn set_last_restart_slot(&mut self, last_restart_slot: LastRestartSlot) { + self.last_restart_slot = Some(Arc::new(last_restart_slot)); + } + pub fn get_slot_hashes(&self) -> Result, InstructionError> { self.slot_hashes .clone() @@ -165,6 +178,13 @@ impl SysvarCache { } }); } + if self.last_restart_slot.is_none() { + get_account_data(&LastRestartSlot::id(), &mut |data: &[u8]| { + if let Ok(last_restart_slot) = bincode::deserialize(data) { + self.set_last_restart_slot(last_restart_slot); + } + }); + } } pub fn reset(&mut self) { @@ -258,4 +278,17 @@ pub mod get_sysvar_with_account_check { )?; invoke_context.get_sysvar_cache().get_stake_history() } + + pub fn last_restart_slot( + invoke_context: &InvokeContext, + instruction_context: &InstructionContext, + instruction_account_index: IndexOfAccount, + ) -> Result, InstructionError> { + check_sysvar_account::( + invoke_context.transaction_context, + instruction_context, + instruction_account_index, + )?; + invoke_context.get_sysvar_cache().get_last_restart_slot() + } } diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index ec5de690b4..420556fa13 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -371,6 +371,15 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { get_sysvar(get_invoke_context().get_sysvar_cache().get_rent(), var_addr) } + fn sol_get_last_restart_slot(&self, var_addr: *mut u8) -> u64 { + get_sysvar( + get_invoke_context() + .get_sysvar_cache() + .get_last_restart_slot(), + var_addr, + ) + } + fn sol_get_return_data(&self) -> Option<(Pubkey, Vec)> { let (program_id, data) = get_invoke_context().transaction_context.get_return_data(); Some((*program_id, data.to_vec())) @@ -1162,4 +1171,12 @@ impl ProgramTestContext { self.last_blockhash = blockhash; Ok(blockhash) } + + /// record a hard fork slot in working bank; should be in the past + pub fn register_hard_fork(&mut self, hard_fork_slot: Slot) { + let bank_forks = self.bank_forks.write().unwrap(); + let hard_forks = bank_forks.working_bank().hard_forks(); + let mut write = hard_forks.write().unwrap(); + write.register(hard_fork_slot); + } } diff --git a/program-test/tests/sysvar_last_restart_slot.rs b/program-test/tests/sysvar_last_restart_slot.rs new file mode 100644 index 0000000000..438c10485e --- /dev/null +++ b/program-test/tests/sysvar_last_restart_slot.rs @@ -0,0 +1,104 @@ +use { + solana_program_test::{processor, ProgramTest, ProgramTestContext}, + solana_sdk::{ + account_info::AccountInfo, + clock::Slot, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + pubkey::Pubkey, + signature::Signer, + sysvar::{last_restart_slot, last_restart_slot::LastRestartSlot, Sysvar}, + transaction::Transaction, + }, +}; + +// program to check both syscall and sysvar +fn sysvar_last_restart_slot_process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + input: &[u8], +) -> ProgramResult { + msg!("sysvar_last_restart_slot"); + assert_eq!(input.len(), 8); + let expected_last_hardfork_slot = u64::from_le_bytes(input[0..8].try_into().unwrap()); + + let last_restart_slot = LastRestartSlot::get(); + msg!("last restart slot: {:?}", last_restart_slot); + assert_eq!( + last_restart_slot, + Ok(LastRestartSlot { + last_restart_slot: expected_last_hardfork_slot + }) + ); + + let last_restart_slot_account = &accounts[0]; + let slot_via_account = LastRestartSlot::from_account_info(last_restart_slot_account)?; + msg!("slot via account: {:?}", slot_via_account); + + assert_eq!( + slot_via_account, + LastRestartSlot { + last_restart_slot: expected_last_hardfork_slot + } + ); + + Ok(()) +} + +async fn check_with_program( + context: &mut ProgramTestContext, + program_id: Pubkey, + expected_last_restart_slot: u64, +) { + let instructions = vec![Instruction::new_with_bincode( + program_id, + &expected_last_restart_slot.to_le_bytes(), + vec![AccountMeta::new(last_restart_slot::id(), false)], + )]; + + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); +} + +#[tokio::test] +async fn get_sysvar_last_restart_slot() { + let program_id = Pubkey::new_unique(); + let program_test = ProgramTest::new( + "sysvar_last_restart_slot_process", + program_id, + processor!(sysvar_last_restart_slot_process_instruction), + ); + + let mut context = program_test.start_with_context().await; + + check_with_program(&mut context, program_id, 0).await; + context.warp_to_slot(40).unwrap(); + context.register_hard_fork(41 as Slot); + check_with_program(&mut context, program_id, 0).await; + context.warp_to_slot(41).unwrap(); + check_with_program(&mut context, program_id, 41).await; + // check for value lower than previous hardfork + context.register_hard_fork(40 as Slot); + context.warp_to_slot(45).unwrap(); + check_with_program(&mut context, program_id, 41).await; + context.register_hard_fork(43 as Slot); + context.register_hard_fork(47 as Slot); + context.warp_to_slot(46).unwrap(); + check_with_program(&mut context, program_id, 43).await; + context.register_hard_fork(50 as Slot); + context.warp_to_slot(48).unwrap(); + check_with_program(&mut context, program_id, 47).await; + context.warp_to_slot(50).unwrap(); + check_with_program(&mut context, program_id, 50).await; +} diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index 654d881144..90f3015c1d 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -6,7 +6,7 @@ pub use self::{ mem_ops::{SyscallMemcmp, SyscallMemcpy, SyscallMemmove, SyscallMemset}, sysvar::{ SyscallGetClockSysvar, SyscallGetEpochScheduleSysvar, SyscallGetFeesSysvar, - SyscallGetRentSysvar, + SyscallGetLastRestartSlotSysvar, SyscallGetRentSysvar, }, }; #[allow(deprecated)] @@ -37,9 +37,10 @@ use { disable_cpi_setting_executable_and_rent_epoch, disable_deploy_of_alloc_free_syscall, disable_fees_sysvar, enable_alt_bn128_syscall, enable_big_mod_exp_syscall, enable_early_verification_of_account_modifications, - error_on_syscall_bpf_function_hash_collisions, libsecp256k1_0_5_upgrade_enabled, - reject_callx_r10, stop_sibling_instruction_search_at_parent, - stop_truncating_strings_in_syscalls, switch_to_new_elf_parser, + error_on_syscall_bpf_function_hash_collisions, last_restart_slot_sysvar, + libsecp256k1_0_5_upgrade_enabled, reject_callx_r10, + stop_sibling_instruction_search_at_parent, stop_truncating_strings_in_syscalls, + switch_to_new_elf_parser, }, hash::{Hasher, HASH_BYTES}, instruction::{ @@ -187,6 +188,7 @@ pub fn create_program_runtime_environment<'a>( let disable_fees_sysvar = feature_set.is_active(&disable_fees_sysvar::id()); let disable_deploy_of_alloc_free_syscall = reject_deployment_of_broken_elfs && feature_set.is_active(&disable_deploy_of_alloc_free_syscall::id()); + let last_restart_slot_syscall_enabled = feature_set.is_active(&last_restart_slot_sysvar::id()); let mut result = BuiltinProgram::new_loader(config); @@ -263,6 +265,13 @@ pub fn create_program_runtime_environment<'a>( )?; result.register_function(b"sol_get_rent_sysvar", SyscallGetRentSysvar::call)?; + register_feature_gated_function!( + result, + last_restart_slot_syscall_enabled, + b"sol_get_last_restart_slot", + SyscallGetLastRestartSlotSysvar::call, + )?; + // Memory ops result.register_function(b"sol_memcpy_", SyscallMemcpy::call)?; result.register_function(b"sol_memmove_", SyscallMemmove::call)?; diff --git a/programs/bpf_loader/src/syscalls/sysvar.rs b/programs/bpf_loader/src/syscalls/sysvar.rs index 9ab061b8a2..03973820e4 100644 --- a/programs/bpf_loader/src/syscalls/sysvar.rs +++ b/programs/bpf_loader/src/syscalls/sysvar.rs @@ -112,3 +112,25 @@ declare_syscall!( ) } ); + +declare_syscall!( + /// Get a Last Restart Slot sysvar + SyscallGetLastRestartSlotSysvar, + fn inner_call( + invoke_context: &mut InvokeContext, + var_addr: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &mut MemoryMapping, + ) -> Result { + get_sysvar( + invoke_context.get_sysvar_cache().get_last_restart_slot(), + var_addr, + invoke_context.get_check_aligned(), + memory_mapping, + invoke_context, + ) + } +); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index c6e486d238..0056affe2b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -156,7 +156,7 @@ use { slot_history::{Check, SlotHistory}, stake::state::Delegation, system_transaction, - sysvar::{self, Sysvar, SysvarId}, + sysvar::{self, last_restart_slot::LastRestartSlot, Sysvar, SysvarId}, timing::years_as_slots, transaction::{ self, MessageHash, Result, SanitizedTransaction, Transaction, TransactionError, @@ -1447,6 +1447,7 @@ impl Bank { bank.update_rent(); bank.update_epoch_schedule(); bank.update_recent_blockhashes(); + bank.update_last_restart_slot(); bank.fill_missing_sysvar_cache_entries(); bank } @@ -1744,6 +1745,7 @@ impl Bank { new.update_stake_history(Some(parent_epoch)); new.update_clock(Some(parent_epoch)); new.update_fees(); + new.update_last_restart_slot() }); let (_, fill_sysvar_cache_time_us) = measure_us!(new.fill_missing_sysvar_cache_entries()); @@ -2415,6 +2417,35 @@ impl Bank { }); } + 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 last_restart_slot = { + let slot = self.slot; + let hard_forks = self.hard_forks(); + let hard_forks_r = hard_forks.read().unwrap(); + + // Only consider hard forks <= this bank's slot to avoid prematurely applying + // a hard fork that is set to occur in the future. + hard_forks_r + .iter() + .rev() + .find(|(hard_fork, _)| *hard_fork <= slot) + .map(|(slot, _)| *slot) + .unwrap_or(0) + }; + 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(&self, sysvar: &T) where T: Sysvar + SysvarId, diff --git a/sdk/program/src/last_restart_slot.rs b/sdk/program/src/last_restart_slot.rs new file mode 100644 index 0000000000..7c67a574e9 --- /dev/null +++ b/sdk/program/src/last_restart_slot.rs @@ -0,0 +1,10 @@ +//! Information about the last restart slot (hard fork). + +use {crate::clock::Slot, solana_sdk_macro::CloneZeroed}; + +#[repr(C)] +#[derive(Serialize, Deserialize, Debug, CloneZeroed, PartialEq, Eq, Default)] +pub struct LastRestartSlot { + /// The last restart `Slot`. + pub last_restart_slot: Slot, +} diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 2bd93bae68..2d0a679e46 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -495,6 +495,7 @@ pub mod incinerator; pub mod instruction; pub mod keccak; pub mod lamports; +pub mod last_restart_slot; pub mod loader_instruction; pub mod loader_upgradeable_instruction; pub mod loader_v4; diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index e475882019..a61b2e1581 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -54,7 +54,9 @@ pub trait SyscallStubs: Sync + Send { fn sol_get_epoch_rewards_sysvar(&self, _var_addr: *mut u8) -> u64 { UNSUPPORTED_SYSVAR } - + fn sol_get_last_restart_slot(&self, _var_addr: *mut u8) -> u64 { + UNSUPPORTED_SYSVAR + } /// # Safety unsafe fn sol_memcpy(&self, dst: *mut u8, src: *const u8, n: usize) { // cannot be overlapping @@ -154,6 +156,13 @@ pub(crate) fn sol_get_rent_sysvar(var_addr: *mut u8) -> u64 { SYSCALL_STUBS.read().unwrap().sol_get_rent_sysvar(var_addr) } +pub(crate) fn sol_get_last_restart_slot(var_addr: *mut u8) -> u64 { + SYSCALL_STUBS + .read() + .unwrap() + .sol_get_last_restart_slot(var_addr) +} + 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 d9292108b1..21184f0650 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -50,6 +50,7 @@ define_syscall!(fn sol_get_clock_sysvar(addr: *mut u8) -> u64); define_syscall!(fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64); define_syscall!(fn sol_get_fees_sysvar(addr: *mut u8) -> u64); define_syscall!(fn sol_get_rent_sysvar(addr: *mut u8) -> u64); +define_syscall!(fn sol_get_last_restart_slot(addr: *mut u8) -> u64); define_syscall!(fn sol_memcpy_(dst: *mut u8, src: *const u8, n: u64)); define_syscall!(fn sol_memmove_(dst: *mut u8, src: *const u8, n: u64)); define_syscall!(fn sol_memcmp_(s1: *const u8, s2: *const u8, n: u64, result: *mut i32)); diff --git a/sdk/program/src/sysvar/last_restart_slot.rs b/sdk/program/src/sysvar/last_restart_slot.rs new file mode 100644 index 0000000000..17c016a6c9 --- /dev/null +++ b/sdk/program/src/sysvar/last_restart_slot.rs @@ -0,0 +1,52 @@ +//! Information about the last restart slot (hard fork). +//! +//! The _last restart sysvar_ provides access to the last restart slot kept in the +//! bank fork for the slot on the fork that executes the current transaction. +//! In case there was no fork it returns _0_. +//! +//! [`LastRestartSlot`] implements [`Sysvar::get`] and can be loaded efficiently without +//! passing the sysvar account ID to the program. +//! +//! See also the Solana [SIMD proposal][simd]. +//! +//! [simd]: https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0047-syscall-and-sysvar-for-last-restart-slot.md +//! +//! # Examples +//! +//! Accessing via on-chain program directly: +//! +//! ```no_run +//! # use solana_program::{ +//! # account_info::{AccountInfo, next_account_info}, +//! # entrypoint::ProgramResult, +//! # msg, +//! # pubkey::Pubkey, +//! # sysvar::Sysvar, +//! # last_restart_slot::LastRestartSlot, +//! # }; +//! +//! fn process_instruction( +//! program_id: &Pubkey, +//! accounts: &[AccountInfo], +//! instruction_data: &[u8], +//! ) -> ProgramResult { +//! +//! let last_restart_slot = LastRestartSlot::get(); +//! msg!("last restart slot: {:?}", last_restart_slot); +//! +//! Ok(()) +//! } +//! ``` +//! + +pub use crate::last_restart_slot::LastRestartSlot; +use crate::{impl_sysvar_get, program_error::ProgramError, sysvar::Sysvar}; + +crate::declare_sysvar_id!( + "SysvarLastRestartS1ot1111111111111111111111", + LastRestartSlot +); + +impl Sysvar for LastRestartSlot { + impl_sysvar_get!(sol_get_last_restart_slot); +} diff --git a/sdk/program/src/sysvar/mod.rs b/sdk/program/src/sysvar/mod.rs index 01376004c9..1bb7c12b33 100644 --- a/sdk/program/src/sysvar/mod.rs +++ b/sdk/program/src/sysvar/mod.rs @@ -91,6 +91,7 @@ pub mod epoch_rewards; pub mod epoch_schedule; pub mod fees; pub mod instructions; +pub mod last_restart_slot; pub mod recent_blockhashes; pub mod rent; pub mod rewards; @@ -113,6 +114,7 @@ lazy_static! { stake_history::id(), instructions::id(), epoch_rewards::id(), + last_restart_slot::id(), ]; } diff --git a/sdk/sbf/c/inc/sol/inc/last_restart_slot.inc b/sdk/sbf/c/inc/sol/inc/last_restart_slot.inc new file mode 100644 index 0000000000..d51245f368 --- /dev/null +++ b/sdk/sbf/c/inc/sol/inc/last_restart_slot.inc @@ -0,0 +1,21 @@ +#pragma once +/** + * @brief Solana Last Restart Slot system call + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get Last Restart Slot + */ +@SYSCALL u64 sol_get_last_restart_slot(uint8_t *result); + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/sbf/c/inc/sol/last_restart_slot.h b/sdk/sbf/c/inc/sol/last_restart_slot.h new file mode 100644 index 0000000000..54b2cf7254 --- /dev/null +++ b/sdk/sbf/c/inc/sol/last_restart_slot.h @@ -0,0 +1,30 @@ +#pragma once +/** + * @brief Solana Last Restart Slot system call + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get Last Restart Slot + */ +/* DO NOT MODIFY THIS GENERATED FILE. INSTEAD CHANGE sdk/sbf/c/inc/sol/inc/last_restart_slot.inc AND RUN `cargo run --bin gen-headers` */ +#ifndef SOL_SBFV2 +u64 sol_get_last_restart_slot(uint8_t *result); +#else +typedef u64(*sol_get_last_restart_slot_pointer_type)(uint8_t *result); +static u64 sol_get_last_restart_slot(uint8_t *result arg1) { + sol_get_last_restart_slot_pointer_type sol_get_last_restart_slot_pointer = (sol_get_last_restart_slot_pointer_type) 411697201; + return sol_get_last_restart_slot_pointer(arg1); +} +#endif + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 6ebec70563..6957b951e8 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -668,6 +668,10 @@ pub mod checked_arithmetic_in_fee_validation { solana_sdk::declare_id!("5Pecy6ie6XGm22pc9d4P9W5c31BugcFBuy6hsP2zkETv"); } +pub mod last_restart_slot_sysvar { + solana_sdk::declare_id!("HooKD5NC9QNxk25QuzCssB8ecrEzGt6eXEPBUxWp1LaR"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -830,6 +834,7 @@ lazy_static! { (vote_state_add_vote_latency::id(), "replace Lockout with LandedVote (including vote latency) in vote state #31264"), (checked_arithmetic_in_fee_validation::id(), "checked arithmetic in fee validation #31273"), (bpf_account_data_direct_mapping::id(), "use memory regions to map account data into the rbpf vm instead of copying the data"), + (last_restart_slot_sysvar::id(), "enable new sysvar last_restart_slot"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()