diff --git a/Cargo.lock b/Cargo.lock index 2f985adc55..8abd0a2b87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4988,10 +4988,13 @@ version = "1.6.5" dependencies = [ "async-trait", "base64 0.12.3", + "bincode", "chrono", "chrono-humanize", "log 0.4.11", "mio 0.7.6", + "serde", + "serde_derive", "solana-banks-client", "solana-banks-server", "solana-bpf-loader-program", diff --git a/program-test/Cargo.toml b/program-test/Cargo.toml index cf004e0f66..019a97ef67 100644 --- a/program-test/Cargo.toml +++ b/program-test/Cargo.toml @@ -10,10 +10,13 @@ version = "1.6.5" [dependencies] async-trait = "0.1.42" base64 = "0.12.3" +bincode = "1.3.1" chrono = "0.4.19" chrono-humanize = "0.1.1" log = "0.4.11" mio = "0.7.6" +serde = "1.0.112" +serde_derive = "1.0.103" solana-banks-client = { path = "../banks-client", version = "=1.6.5" } solana-banks-server = { path = "../banks-server", version = "=1.6.5" } solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.6.5" } diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 70782e1ad9..95ccf61662 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -16,8 +16,9 @@ use { solana_sdk::{ account::{Account, AccountSharedData, ReadableAccount}, account_info::AccountInfo, - clock::Slot, - entrypoint::ProgramResult, + clock::{Clock, Slot}, + entrypoint::{ProgramResult, SUCCESS}, + epoch_schedule::EpochSchedule, feature_set::demote_sysvar_write_locks, fee_calculator::{FeeCalculator, FeeRateGovernor}, genesis_config::{ClusterType, GenesisConfig}, @@ -30,10 +31,15 @@ use { process_instruction::{ stable_log, BpfComputeBudget, InvokeContext, ProcessInstructionWithContext, }, - program_error::ProgramError, + program_error::{ProgramError, ACCOUNT_BORROW_FAILED, UNSUPPORTED_SYSVAR}, pubkey::Pubkey, rent::Rent, signature::{Keypair, Signer}, + sysvar::{ + clock, epoch_schedule, + fees::{self, Fees}, + rent, Sysvar, + }, }, solana_vote_program::vote_state::{VoteState, VoteStateVersions}, std::{ @@ -176,6 +182,43 @@ macro_rules! processor { }; } +fn get_sysvar( + id: &Pubkey, + var_addr: *mut u8, +) -> u64 { + let invoke_context = get_invoke_context(); + + let sysvar_data = match invoke_context.get_sysvar_data(id).ok_or_else(|| { + ic_msg!(invoke_context, "Unable to get Sysvar {}", id); + UNSUPPORTED_SYSVAR + }) { + Ok(sysvar_data) => sysvar_data, + Err(err) => return err, + }; + + let var: T = match bincode::deserialize(&sysvar_data) { + Ok(sysvar_data) => sysvar_data, + Err(_) => return UNSUPPORTED_SYSVAR, + }; + + unsafe { + *(var_addr as *mut _ as *mut T) = var; + } + + if invoke_context + .get_compute_meter() + .try_borrow_mut() + .map_err(|_| ACCOUNT_BORROW_FAILED) + .unwrap() + .consume(invoke_context.get_bpf_compute_budget().sysvar_base_cost + T::size_of() as u64) + .is_err() + { + panic!("Exceeded compute budget"); + } + + SUCCESS +} + struct SyscallStubs {} impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { fn sol_log(&self, message: &str) { @@ -328,6 +371,22 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { stable_log::program_success(&logger, &program_id); Ok(()) } + + fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 { + get_sysvar::(&clock::id(), var_addr) + } + + fn sol_get_epoch_schedule_sysvar(&self, var_addr: *mut u8) -> u64 { + get_sysvar::(&epoch_schedule::id(), var_addr) + } + + fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 { + get_sysvar::(&fees::id(), var_addr) + } + + fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 { + get_sysvar::(&rent::id(), var_addr) + } } pub fn find_file(filename: &str) -> Option { diff --git a/program-test/tests/sysvar.rs b/program-test/tests/sysvar.rs new file mode 100644 index 0000000000..9710523e81 --- /dev/null +++ b/program-test/tests/sysvar.rs @@ -0,0 +1,72 @@ +use { + solana_program_test::{processor, ProgramTest}, + solana_sdk::{ + account_info::AccountInfo, + clock::Clock, + entrypoint::ProgramResult, + epoch_schedule::EpochSchedule, + fee_calculator::FeeCalculator, + instruction::Instruction, + msg, + pubkey::Pubkey, + rent::Rent, + signature::Signer, + sysvar::{fees::Fees, Sysvar}, + transaction::Transaction, + }, +}; + +// Process instruction to invoke into another program +fn sysvar_getter_process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _input: &[u8], +) -> ProgramResult { + msg!("sysvar_getter"); + + let clock = Clock::get()?; + assert_eq!(42, clock.slot); + + let epoch_schedule = EpochSchedule::get()?; + assert_eq!(epoch_schedule, EpochSchedule::default()); + + let fees = Fees::get()?; + assert_eq!( + fees.fee_calculator, + FeeCalculator { + lamports_per_signature: 5000 + } + ); + + let rent = Rent::get()?; + assert_eq!(rent, Rent::default()); + + Ok(()) +} + +#[tokio::test] +async fn get_sysvar() { + let program_id = Pubkey::new_unique(); + let program_test = ProgramTest::new( + "sysvar_getter", + program_id, + processor!(sysvar_getter_process_instruction), + ); + + let mut context = program_test.start_with_context().await; + context.warp_to_slot(42).unwrap(); + let instructions = vec![Instruction::new_with_bincode(program_id, &(), vec![])]; + + 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(); +} diff --git a/programs/bpf/rust/sysvar/src/lib.rs b/programs/bpf/rust/sysvar/src/lib.rs index 9513405f52..26ec6b6712 100644 --- a/programs/bpf/rust/sysvar/src/lib.rs +++ b/programs/bpf/rust/sysvar/src/lib.rs @@ -3,62 +3,83 @@ extern crate solana_program; use solana_program::{ account_info::AccountInfo, - clock::DEFAULT_SLOTS_PER_EPOCH, entrypoint, entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey, - rent, sysvar::{ - self, clock::Clock, fees::Fees, instructions, rent::Rent, slot_hashes::SlotHashes, + self, clock::Clock, epoch_schedule::EpochSchedule, fees::Fees, instructions, + recent_blockhashes::RecentBlockhashes, rent::Rent, slot_hashes::SlotHashes, slot_history::SlotHistory, stake_history::StakeHistory, Sysvar, }, }; entrypoint!(process_instruction); #[allow(clippy::unnecessary_wraps)] -fn process_instruction( +pub fn process_instruction( _program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8], ) -> ProgramResult { // Clock - msg!("Clock identifier:"); - sysvar::clock::id().log(); - let clock = Clock::from_account_info(&accounts[2]).unwrap(); - assert_eq!(clock.slot, DEFAULT_SLOTS_PER_EPOCH + 1); + { + msg!("Clock identifier:"); + sysvar::clock::id().log(); + let clock = Clock::from_account_info(&accounts[2]).unwrap(); + assert_ne!(clock, Clock::default()); + let got_clock = Clock::get()?; + assert_eq!(clock, got_clock); + } + + // Epoch Schedule + { + msg!("EpochSchedule identifier:"); + sysvar::epoch_schedule::id().log(); + let epoch_schedule = EpochSchedule::from_account_info(&accounts[3]).unwrap(); + assert_eq!(epoch_schedule, EpochSchedule::default()); + let got_epoch_schedule = EpochSchedule::get()?; + assert_eq!(epoch_schedule, got_epoch_schedule); + } // Fees - msg!("Fees identifier:"); - sysvar::fees::id().log(); - let fees = Fees::from_account_info(&accounts[3]).unwrap(); - let fee_calculator = fees.fee_calculator; - assert_eq!(fee_calculator.lamports_per_signature, 0); + { + msg!("Fees identifier:"); + sysvar::fees::id().log(); + let fees = Fees::from_account_info(&accounts[4]).unwrap(); + let got_fees = Fees::get()?; + assert_eq!(fees, got_fees); + } // Instructions msg!("Instructions identifier:"); sysvar::instructions::id().log(); - let index = instructions::load_current_index(&accounts[4].try_borrow_data()?); + let index = instructions::load_current_index(&accounts[5].try_borrow_data()?); assert_eq!(0, index); - msg!( - "instruction {:?}", - instructions::load_instruction_at(index as usize, &accounts[4].try_borrow_data()?) - ); - let due = Rent::from_account_info(&accounts[5]).unwrap().due( - rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR * rent::DEFAULT_EXEMPTION_THRESHOLD as u64, - 1, - 1.0, - ); - assert_eq!(due, (0, true)); + // Recent Blockhashes + { + msg!("RecentBlockhashes identifier:"); + sysvar::recent_blockhashes::id().log(); + let recent_blockhashes = RecentBlockhashes::from_account_info(&accounts[6]).unwrap(); + assert_ne!(recent_blockhashes, RecentBlockhashes::default()); + } + + // Rent + { + msg!("Rent identifier:"); + sysvar::rent::id().log(); + let rent = Rent::from_account_info(&accounts[7]).unwrap(); + let got_rent = Rent::get()?; + assert_eq!(rent, got_rent); + } // Slot Hashes msg!("SlotHashes identifier:"); sysvar::slot_hashes::id().log(); assert_eq!( Err(ProgramError::UnsupportedSysvar), - SlotHashes::from_account_info(&accounts[6]) + SlotHashes::from_account_info(&accounts[8]) ); // Slot History @@ -66,14 +87,13 @@ fn process_instruction( sysvar::slot_history::id().log(); assert_eq!( Err(ProgramError::UnsupportedSysvar), - SlotHistory::from_account_info(&accounts[7]) + SlotHistory::from_account_info(&accounts[9]) ); // Stake History msg!("StakeHistory identifier:"); sysvar::stake_history::id().log(); - let stake_history = StakeHistory::from_account_info(&accounts[8]).unwrap(); - assert!(stake_history.len() >= 1); + let _ = StakeHistory::from_account_info(&accounts[10]).unwrap(); Ok(()) } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 0119cb82d3..0060196b20 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -35,7 +35,10 @@ use solana_sdk::{ pubkey::Pubkey, signature::{keypair_from_seed, Keypair, Signer}, system_instruction, - sysvar::{clock, fees, rent, slot_hashes, slot_history, stake_history, instructions}, + sysvar::{ + clock, epoch_schedule, fees, instructions, recent_blockhashes, rent, slot_hashes, + slot_history, stake_history, + }, transaction::{Transaction, TransactionError}, }; use solana_transaction_status::{ @@ -466,13 +469,14 @@ fn test_program_bpf_sanity() { AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new(Keypair::new().pubkey(), false), AccountMeta::new_readonly(clock::id(), false), + AccountMeta::new_readonly(epoch_schedule::id(), false), AccountMeta::new_readonly(fees::id(), false), AccountMeta::new_readonly(instructions::id(), false), + AccountMeta::new_readonly(recent_blockhashes::id(), false), AccountMeta::new_readonly(rent::id(), false), AccountMeta::new_readonly(slot_hashes::id(), false), AccountMeta::new_readonly(slot_history::id(), false), AccountMeta::new_readonly(stake_history::id(), false), - ]; let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 92506f4d06..a16ce07e4d 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -15,10 +15,12 @@ use solana_sdk::{ account_utils::StateMut, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + clock::Clock, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, + epoch_schedule::EpochSchedule, feature_set::{ cpi_data_cost, cpi_share_ro_and_exec_accounts, demote_sysvar_write_locks, - ristretto_mul_syscall_enabled, + ristretto_mul_syscall_enabled, sysvar_via_syscall, }, hash::{Hasher, HASH_BYTES}, ic_msg, @@ -27,6 +29,8 @@ use solana_sdk::{ native_loader, process_instruction::{stable_log, ComputeMeter, InvokeContext, Logger}, pubkey::{Pubkey, PubkeyError, MAX_SEEDS}, + rent::Rent, + sysvar::{self, fees::Fees, Sysvar, SysvarId}, }; use std::{ alloc::Layout, @@ -127,6 +131,19 @@ pub fn register_syscalls( .register_syscall_by_name(b"sol_ristretto_mul", SyscallRistrettoMul::call)?; } + if invoke_context.is_feature_active(&sysvar_via_syscall::id()) { + syscall_registry + .register_syscall_by_name(b"sol_get_clock_sysvar", SyscallGetClockSysvar::call)?; + syscall_registry.register_syscall_by_name( + b"sol_get_epoch_schedule_sysvar", + SyscallGetEpochScheduleSysvar::call, + )?; + syscall_registry + .register_syscall_by_name(b"sol_get_fees_sysvar", SyscallGetFeesSysvar::call)?; + syscall_registry + .register_syscall_by_name(b"sol_get_rent_sysvar", SyscallGetRentSysvar::call)?; + } + syscall_registry .register_syscall_by_name(b"sol_invoke_signed_c", SyscallInvokeSignedC::call)?; syscall_registry @@ -137,8 +154,8 @@ pub fn register_syscalls( } macro_rules! bind_feature_gated_syscall_context_object { - ($vm:expr, $invoke_context:expr, $feature_id:expr, $syscall_context_object:expr $(,)?) => { - if $invoke_context.is_feature_active($feature_id) { + ($vm:expr, $is_feature_active:expr, $syscall_context_object:expr $(,)?) => { + if $is_feature_active { match $vm.bind_syscall_context_object($syscall_context_object, None) { Err(EbpfError::SyscallNotRegistered(_)) | Ok(()) => {} Err(err) => { @@ -234,8 +251,7 @@ pub fn bind_syscall_context_objects<'a>( bind_feature_gated_syscall_context_object!( vm, - invoke_context, - &ristretto_mul_syscall_enabled::id(), + invoke_context.is_feature_active(&ristretto_mul_syscall_enabled::id()), Box::new(SyscallRistrettoMul { cost: 0, compute_meter: invoke_context.get_compute_meter(), @@ -243,9 +259,44 @@ pub fn bind_syscall_context_objects<'a>( }), ); - // Cross-program invocation syscalls + let is_sysvar_via_syscall_active = invoke_context.is_feature_active(&sysvar_via_syscall::id()); let invoke_context = Rc::new(RefCell::new(invoke_context)); + + bind_feature_gated_syscall_context_object!( + vm, + is_sysvar_via_syscall_active, + Box::new(SyscallGetClockSysvar { + invoke_context: invoke_context.clone(), + loader_id, + }), + ); + bind_feature_gated_syscall_context_object!( + vm, + is_sysvar_via_syscall_active, + Box::new(SyscallGetEpochScheduleSysvar { + invoke_context: invoke_context.clone(), + loader_id, + }), + ); + bind_feature_gated_syscall_context_object!( + vm, + is_sysvar_via_syscall_active, + Box::new(SyscallGetFeesSysvar { + invoke_context: invoke_context.clone(), + loader_id, + }), + ); + bind_feature_gated_syscall_context_object!( + vm, + is_sysvar_via_syscall_active, + Box::new(SyscallGetRentSysvar { + invoke_context: invoke_context.clone(), + loader_id, + }), + ); + + // Cross-program invocation syscalls vm.bind_syscall_context_object( Box::new(SyscallInvokeSignedC { callers_keyed_accounts, @@ -825,6 +876,136 @@ impl<'a> SyscallObject for SyscallRistrettoMul<'a> { } } +fn get_sysvar( + id: &Pubkey, + var_addr: u64, + loader_id: &Pubkey, + memory_mapping: &MemoryMapping, + invoke_context: Rc>, +) -> Result> { + let mut invoke_context = invoke_context + .try_borrow_mut() + .map_err(|_| SyscallError::InvokeContextBorrowFailed)?; + + invoke_context.get_compute_meter().consume( + invoke_context.get_bpf_compute_budget().sysvar_base_cost + size_of::() as u64, + )?; + let var = translate_type_mut::(memory_mapping, var_addr, loader_id)?; + + let sysvar_data = invoke_context.get_sysvar_data(id).ok_or_else(|| { + ic_msg!(invoke_context, "Unable to get Sysvar {}", id); + SyscallError::InstructionError(InstructionError::UnsupportedSysvar) + })?; + + *var = bincode::deserialize(&sysvar_data).map_err(|e| { + ic_msg!(invoke_context, "Unable to get Sysvar {}: {:?}", id, e); + SyscallError::InstructionError(InstructionError::UnsupportedSysvar) + })?; + + Ok(SUCCESS) +} + +/// Get a Clock sysvar +struct SyscallGetClockSysvar<'a> { + invoke_context: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallGetClockSysvar<'a> { + fn call( + &mut self, + var_addr: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + *result = get_sysvar::( + &sysvar::clock::id(), + var_addr, + self.loader_id, + memory_mapping, + self.invoke_context.clone(), + ); + } +} +/// Get a EpochSchedule sysvar +struct SyscallGetEpochScheduleSysvar<'a> { + invoke_context: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallGetEpochScheduleSysvar<'a> { + fn call( + &mut self, + var_addr: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + *result = get_sysvar::( + &sysvar::epoch_schedule::id(), + var_addr, + self.loader_id, + memory_mapping, + self.invoke_context.clone(), + ); + } +} +/// Get a Fees sysvar +struct SyscallGetFeesSysvar<'a> { + invoke_context: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallGetFeesSysvar<'a> { + fn call( + &mut self, + var_addr: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + *result = get_sysvar::( + &sysvar::fees::id(), + var_addr, + self.loader_id, + memory_mapping, + self.invoke_context.clone(), + ); + } +} +/// Get a Rent sysvar +struct SyscallGetRentSysvar<'a> { + invoke_context: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallGetRentSysvar<'a> { + fn call( + &mut self, + var_addr: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + *result = get_sysvar::( + &sysvar::rent::id(), + var_addr, + self.loader_id, + memory_mapping, + self.invoke_context.clone(), + ); + } +} + // Cross-program invocation syscalls struct AccountReferences<'a> { @@ -1739,8 +1920,9 @@ mod tests { use solana_rbpf::{memory_region::MemoryRegion, user_error::UserError, vm::Config}; use solana_sdk::{ bpf_loader, + fee_calculator::FeeCalculator, hash::hashv, - process_instruction::{MockComputeMeter, MockLogger}, + process_instruction::{MockComputeMeter, MockInvokeContext, MockLogger}, }; use std::str::FromStr; @@ -2474,4 +2656,175 @@ mod tests { result ); } + + #[test] + fn test_syscall_get_sysvar() { + // Test clock sysvar + { + let got_clock = Clock::default(); + let got_clock_va = 2048; + + let memory_mapping = MemoryMapping::new( + vec![MemoryRegion { + host_addr: &got_clock as *const _ as u64, + vm_addr: got_clock_va, + len: size_of::() as u64, + vm_gap_shift: 63, + is_writable: true, + }], + &DEFAULT_CONFIG, + ); + + let src_clock = Clock { + slot: 1, + epoch_start_timestamp: 2, + epoch: 3, + leader_schedule_epoch: 4, + unix_timestamp: 5, + }; + let mut invoke_context = MockInvokeContext::default(); + let mut data = vec![]; + bincode::serialize_into(&mut data, &src_clock).unwrap(); + invoke_context + .sysvars + .push((sysvar::clock::id(), Some(Rc::new(data)))); + + let mut syscall = SyscallGetClockSysvar { + invoke_context: Rc::new(RefCell::new(&mut invoke_context)), + loader_id: &bpf_loader::id(), + }; + let mut result: Result> = Ok(0); + + syscall.call(got_clock_va, 0, 0, 0, 0, &memory_mapping, &mut result); + result.unwrap(); + assert_eq!(got_clock, src_clock); + } + + // Test epoch_schedule sysvar + { + let got_epochschedule = EpochSchedule::default(); + let got_epochschedule_va = 2048; + + let memory_mapping = MemoryMapping::new( + vec![MemoryRegion { + host_addr: &got_epochschedule as *const _ as u64, + vm_addr: got_epochschedule_va, + len: size_of::() as u64, + vm_gap_shift: 63, + is_writable: true, + }], + &DEFAULT_CONFIG, + ); + + let src_epochschedule = EpochSchedule { + slots_per_epoch: 1, + leader_schedule_slot_offset: 2, + warmup: false, + first_normal_epoch: 3, + first_normal_slot: 4, + }; + let mut invoke_context = MockInvokeContext::default(); + let mut data = vec![]; + bincode::serialize_into(&mut data, &src_epochschedule).unwrap(); + invoke_context + .sysvars + .push((sysvar::epoch_schedule::id(), Some(Rc::new(data)))); + + let mut syscall = SyscallGetEpochScheduleSysvar { + invoke_context: Rc::new(RefCell::new(&mut invoke_context)), + loader_id: &bpf_loader::id(), + }; + let mut result: Result> = Ok(0); + + syscall.call( + got_epochschedule_va, + 0, + 0, + 0, + 0, + &memory_mapping, + &mut result, + ); + result.unwrap(); + assert_eq!(got_epochschedule, src_epochschedule); + } + + // Test fees sysvar + { + let got_fees = Fees::default(); + let got_fees_va = 2048; + + let memory_mapping = MemoryMapping::new( + vec![MemoryRegion { + host_addr: &got_fees as *const _ as u64, + vm_addr: got_fees_va, + len: size_of::() as u64, + vm_gap_shift: 63, + is_writable: true, + }], + &DEFAULT_CONFIG, + ); + + let src_fees = Fees { + fee_calculator: FeeCalculator { + lamports_per_signature: 1, + }, + }; + let mut invoke_context = MockInvokeContext::default(); + let mut data = vec![]; + bincode::serialize_into(&mut data, &src_fees).unwrap(); + invoke_context + .sysvars + .push((sysvar::fees::id(), Some(Rc::new(data)))); + + let mut syscall = SyscallGetFeesSysvar { + invoke_context: Rc::new(RefCell::new(&mut invoke_context)), + loader_id: &bpf_loader::id(), + }; + let mut result: Result> = Ok(0); + + syscall.call(got_fees_va, 0, 0, 0, 0, &memory_mapping, &mut result); + result.unwrap(); + assert_eq!(got_fees, src_fees); + } + + // Test rent sysvar + { + let got_rent = Rent::default(); + let got_rent_va = 2048; + + let memory_mapping = MemoryMapping::new( + vec![MemoryRegion { + host_addr: &got_rent as *const _ as u64, + vm_addr: got_rent_va, + len: size_of::() as u64, + vm_gap_shift: 63, + is_writable: true, + }], + &DEFAULT_CONFIG, + ); + + let src_rent = Rent { + lamports_per_byte_year: 1, + exemption_threshold: 2.0, + burn_percent: 3, + }; + let mut invoke_context = MockInvokeContext::default(); + let mut data = vec![]; + bincode::serialize_into(&mut data, &src_rent).unwrap(); + invoke_context + .sysvars + .push((sysvar::rent::id(), Some(Rc::new(data)))); + + let mut syscall = SyscallGetRentSysvar { + invoke_context: Rc::new(RefCell::new(&mut invoke_context)), + loader_id: &bpf_loader::id(), + }; + let mut result: Result> = Ok(0); + + syscall.call(got_rent_va, 0, 0, 0, 0, &memory_mapping, &mut result); + result.unwrap(); + assert_eq!(got_rent, src_rent); + } + } } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 937db87b26..1d403a176e 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2987,6 +2987,8 @@ impl Bank { self.feature_set.clone(), bpf_compute_budget, &mut timings.details, + self.rc.accounts.clone(), + &self.ancestors, ); if enable_log_recording { diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 933427fa3f..56e7d78b9e 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -1,6 +1,6 @@ use crate::{ - instruction_recorder::InstructionRecorder, log_collector::LogCollector, - native_loader::NativeLoader, rent_collector::RentCollector, + accounts::Accounts, accounts_index::Ancestors, instruction_recorder::InstructionRecorder, + log_collector::LogCollector, native_loader::NativeLoader, rent_collector::RentCollector, }; use log::*; use serde::{Deserialize, Serialize}; @@ -268,6 +268,9 @@ pub struct ThisInvokeContext<'a> { instruction_recorder: Option, feature_set: Arc, pub timings: ExecuteDetailsTimings, + account_db: Arc, + ancestors: &'a Ancestors, + sysvars: Vec<(Pubkey, Option>>)>, } impl<'a> ThisInvokeContext<'a> { #[allow(clippy::too_many_arguments)] @@ -283,6 +286,8 @@ impl<'a> ThisInvokeContext<'a> { executors: Rc>, instruction_recorder: Option, feature_set: Arc, + account_db: Arc, + ancestors: &'a Ancestors, ) -> Self { let mut program_ids = Vec::with_capacity(bpf_compute_budget.max_invoke_depth); program_ids.push(*program_id); @@ -302,6 +307,9 @@ impl<'a> ThisInvokeContext<'a> { instruction_recorder, feature_set, timings: ExecuteDetailsTimings::default(), + account_db, + ancestors, + sysvars: vec![], } } } @@ -421,6 +429,23 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { self.timings.execute_us += execute_us; self.timings.deserialize_us += deserialize_us; } + fn get_sysvar_data(&mut self, id: &Pubkey) -> Option>> { + // Try share from cache + let mut result = + self.sysvars + .iter() + .find_map(|(key, sysvar)| if id == key { sysvar.clone() } else { None }); + if result.is_none() { + // Load it + result = self + .account_db + .load_slow(self.ancestors, id) + .map(|(account, _)| Rc::new(account.data().clone())); + // Cache it + self.sysvars.push((*id, result.clone())); + } + result + } } pub struct ThisLogger { log_collector: Option>, @@ -1043,6 +1068,8 @@ impl MessageProcessor { bpf_compute_budget: BpfComputeBudget, timings: &mut ExecuteDetailsTimings, demote_sysvar_write_locks: bool, + account_db: Arc, + ancestors: &Ancestors, ) -> Result<(), InstructionError> { // Fixup the special instructions key if present // before the account pre-values are taken care of @@ -1073,6 +1100,8 @@ impl MessageProcessor { executors, instruction_recorder, feature_set, + account_db, + ancestors, ); let keyed_accounts = Self::create_keyed_accounts( message, @@ -1121,6 +1150,8 @@ impl MessageProcessor { feature_set: Arc, bpf_compute_budget: BpfComputeBudget, timings: &mut ExecuteDetailsTimings, + account_db: Arc, + ancestors: &Ancestors, ) -> Result<(), TransactionError> { let demote_sysvar_write_locks = feature_set.is_active(&demote_sysvar_write_locks::id()); for (instruction_index, instruction) in message.instructions.iter().enumerate() { @@ -1142,6 +1173,8 @@ impl MessageProcessor { bpf_compute_budget, timings, demote_sysvar_write_locks, + account_db.clone(), + ancestors, ) .map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?; } @@ -1181,6 +1214,7 @@ mod tests { pre_accounts.push(PreAccount::new(program_id, &account.clone())); } + let ancestors = Ancestors::default(); let mut invoke_context = ThisInvokeContext::new( &program_ids[0], Rent::default(), @@ -1193,6 +1227,8 @@ mod tests { Rc::new(RefCell::new(Executors::default())), None, Arc::new(FeatureSet::all_enabled()), + Arc::new(Accounts::default()), + &ancestors, ); // Check call depth increases and has a limit @@ -1744,6 +1780,7 @@ mod tests { loaders.push(vec![(mock_system_program_id, account)]); let executors = Rc::new(RefCell::new(Executors::default())); + let ancestors = Ancestors::default(); let from_pubkey = solana_sdk::pubkey::new_rand(); let to_pubkey = solana_sdk::pubkey::new_rand(); @@ -1772,6 +1809,8 @@ mod tests { Arc::new(FeatureSet::all_enabled()), BpfComputeBudget::new(), &mut ExecuteDetailsTimings::default(), + Arc::new(Accounts::default()), + &ancestors, ); assert_eq!(result, Ok(())); assert_eq!(accounts[0].borrow().lamports, 100); @@ -1798,6 +1837,8 @@ mod tests { Arc::new(FeatureSet::all_enabled()), BpfComputeBudget::new(), &mut ExecuteDetailsTimings::default(), + Arc::new(Accounts::default()), + &ancestors, ); assert_eq!( result, @@ -1828,6 +1869,8 @@ mod tests { Arc::new(FeatureSet::all_enabled()), BpfComputeBudget::new(), &mut ExecuteDetailsTimings::default(), + Arc::new(Accounts::default()), + &ancestors, ); assert_eq!( result, @@ -1913,6 +1956,7 @@ mod tests { loaders.push(vec![(mock_program_id, account)]); let executors = Rc::new(RefCell::new(Executors::default())); + let ancestors = Ancestors::default(); let from_pubkey = solana_sdk::pubkey::new_rand(); let to_pubkey = solana_sdk::pubkey::new_rand(); @@ -1944,6 +1988,8 @@ mod tests { Arc::new(FeatureSet::all_enabled()), BpfComputeBudget::new(), &mut ExecuteDetailsTimings::default(), + Arc::new(Accounts::default()), + &ancestors, ); assert_eq!( result, @@ -1974,6 +2020,8 @@ mod tests { Arc::new(FeatureSet::all_enabled()), BpfComputeBudget::new(), &mut ExecuteDetailsTimings::default(), + Arc::new(Accounts::default()), + &ancestors, ); assert_eq!(result, Ok(())); @@ -1989,6 +2037,7 @@ mod tests { )], Some(&from_pubkey), ); + let ancestors = Ancestors::default(); let result = message_processor.process_message( &message, &loaders, @@ -2001,6 +2050,8 @@ mod tests { Arc::new(FeatureSet::all_enabled()), BpfComputeBudget::new(), &mut ExecuteDetailsTimings::default(), + Arc::new(Accounts::default()), + &ancestors, ); assert_eq!(result, Ok(())); assert_eq!(accounts[0].borrow().lamports, 80); @@ -2074,6 +2125,7 @@ mod tests { ]; let programs: Vec<(_, ProcessInstructionWithContext)> = vec![(callee_program_id, mock_process_instruction)]; + let ancestors = Ancestors::default(); let mut invoke_context = ThisInvokeContext::new( &caller_program_id, Rent::default(), @@ -2090,6 +2142,8 @@ mod tests { Rc::new(RefCell::new(Executors::default())), None, Arc::new(FeatureSet::all_enabled()), + Arc::new(Accounts::default()), + &ancestors, ); let metas = vec![ AccountMeta::new(owned_key, false), diff --git a/sdk/program/src/clock.rs b/sdk/program/src/clock.rs index eff1459cdb..f923fe5503 100644 --- a/sdk/program/src/clock.rs +++ b/sdk/program/src/clock.rs @@ -82,7 +82,7 @@ pub type UnixTimestamp = i64; /// as the network progresses). /// #[repr(C)] -#[derive(Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Serialize, Clone, Deserialize, Debug, Default, PartialEq)] pub struct Clock { /// the current network/bank Slot pub slot: Slot, diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index fcd63e6b6d..8aa67b6c78 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -2,7 +2,10 @@ #![cfg(not(target_arch = "bpf"))] -use crate::{account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction}; +use crate::{ + account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, + program_error::UNSUPPORTED_SYSVAR, +}; use std::sync::{Arc, RwLock}; lazy_static::lazy_static! { @@ -31,6 +34,19 @@ pub trait SyscallStubs: Sync + Send { sol_log("SyscallStubs: sol_invoke_signed() not available"); Ok(()) } + + fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 { + UNSUPPORTED_SYSVAR + } + fn sol_get_epoch_schedule_sysvar(&self, _var_addr: *mut u8) -> u64 { + UNSUPPORTED_SYSVAR + } + fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 { + UNSUPPORTED_SYSVAR + } + fn sol_get_rent_sysvar(&self, _var_addr: *mut u8) -> u64 { + UNSUPPORTED_SYSVAR + } } struct DefaultSyscallStubs {} @@ -61,3 +77,22 @@ pub(crate) fn sol_invoke_signed( .unwrap() .sol_invoke_signed(instruction, account_infos, signers_seeds) } + +pub(crate) fn sol_get_clock_sysvar(var_addr: *mut u8) -> u64 { + SYSCALL_STUBS.read().unwrap().sol_get_clock_sysvar(var_addr) +} + +pub(crate) fn sol_get_epoch_schedule_sysvar(var_addr: *mut u8) -> u64 { + SYSCALL_STUBS + .read() + .unwrap() + .sol_get_epoch_schedule_sysvar(var_addr) +} + +pub(crate) fn sol_get_fees_sysvar(var_addr: *mut u8) -> u64 { + SYSCALL_STUBS.read().unwrap().sol_get_fees_sysvar(var_addr) +} + +pub(crate) fn sol_get_rent_sysvar(var_addr: *mut u8) -> u64 { + SYSCALL_STUBS.read().unwrap().sol_get_rent_sysvar(var_addr) +} diff --git a/sdk/program/src/sysvar/clock.rs b/sdk/program/src/sysvar/clock.rs index a6af0573bb..89566a0574 100644 --- a/sdk/program/src/sysvar/clock.rs +++ b/sdk/program/src/sysvar/clock.rs @@ -2,8 +2,10 @@ //! pub use crate::clock::Clock; -use crate::sysvar::Sysvar; +use crate::{impl_sysvar_get, program_error::ProgramError, sysvar::Sysvar}; crate::declare_sysvar_id!("SysvarC1ock11111111111111111111111111111111", Clock); -impl Sysvar for Clock {} +impl Sysvar for Clock { + impl_sysvar_get!(sol_get_clock_sysvar); +} diff --git a/sdk/program/src/sysvar/epoch_schedule.rs b/sdk/program/src/sysvar/epoch_schedule.rs index 77b27ec9ce..502d477d9e 100644 --- a/sdk/program/src/sysvar/epoch_schedule.rs +++ b/sdk/program/src/sysvar/epoch_schedule.rs @@ -1,8 +1,11 @@ //! This account contains the current cluster rent //! pub use crate::epoch_schedule::EpochSchedule; -use crate::sysvar::Sysvar; + +use crate::{impl_sysvar_get, program_error::ProgramError, sysvar::Sysvar}; crate::declare_sysvar_id!("SysvarEpochSchedu1e111111111111111111111111", EpochSchedule); -impl Sysvar for EpochSchedule {} +impl Sysvar for EpochSchedule { + impl_sysvar_get!(sol_get_epoch_schedule_sysvar); +} diff --git a/sdk/program/src/sysvar/fees.rs b/sdk/program/src/sysvar/fees.rs index 3c09281e4b..7d6f14d496 100644 --- a/sdk/program/src/sysvar/fees.rs +++ b/sdk/program/src/sysvar/fees.rs @@ -1,11 +1,13 @@ //! This account contains the current cluster fees //! -use crate::{fee_calculator::FeeCalculator, sysvar::Sysvar}; +use crate::{ + fee_calculator::FeeCalculator, impl_sysvar_get, program_error::ProgramError, sysvar::Sysvar, +}; crate::declare_sysvar_id!("SysvarFees111111111111111111111111111111111", Fees); #[repr(C)] -#[derive(Serialize, Deserialize, Debug, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] pub struct Fees { pub fee_calculator: FeeCalculator, } @@ -17,4 +19,6 @@ impl Fees { } } -impl Sysvar for Fees {} +impl Sysvar for Fees { + impl_sysvar_get!(sol_get_fees_sysvar); +} diff --git a/sdk/program/src/sysvar/mod.rs b/sdk/program/src/sysvar/mod.rs index 18735dd145..51f739aef7 100644 --- a/sdk/program/src/sysvar/mod.rs +++ b/sdk/program/src/sysvar/mod.rs @@ -47,14 +47,14 @@ macro_rules! declare_sysvar_id( ) ); -// owner pubkey for sysvar accounts +// Owner pubkey for sysvar accounts crate::declare_id!("Sysvar1111111111111111111111111111111111111"); pub trait SysvarId { fn check_id(pubkey: &Pubkey) -> bool; } -// utilities for moving into and out of Accounts +// Sysvar utilities pub trait Sysvar: SysvarId + Default + Sized + serde::Serialize + serde::de::DeserializeOwned { @@ -70,6 +70,34 @@ pub trait Sysvar: fn to_account_info(&self, account_info: &mut AccountInfo) -> Option<()> { bincode::serialize_into(&mut account_info.data.borrow_mut()[..], self).ok() } + fn get() -> Result { + Err(ProgramError::UnsupportedSysvar) + } +} + +#[macro_export] +macro_rules! impl_sysvar_get { + ($syscall_name:ident) => { + fn get() -> Result { + let mut var = Self::default(); + let var_addr = &mut var as *mut _ as *mut u8; + + #[cfg(target_arch = "bpf")] + let result = unsafe { + extern "C" { + fn $syscall_name(var_addr: *mut u8) -> u64; + } + $syscall_name(var_addr) + }; + #[cfg(not(target_arch = "bpf"))] + let result = crate::program_stubs::$syscall_name(var_addr); + + match result { + crate::entrypoint::SUCCESS => Ok(var), + e => Err(e.into()), + } + } + }; } #[cfg(test)] diff --git a/sdk/program/src/sysvar/rent.rs b/sdk/program/src/sysvar/rent.rs index 7ae3cd209f..13e7f32367 100644 --- a/sdk/program/src/sysvar/rent.rs +++ b/sdk/program/src/sysvar/rent.rs @@ -2,8 +2,10 @@ //! pub use crate::rent::Rent; -use crate::sysvar::Sysvar; +use crate::{impl_sysvar_get, program_error::ProgramError, sysvar::Sysvar}; crate::declare_sysvar_id!("SysvarRent111111111111111111111111111111111", Rent); -impl Sysvar for Rent {} +impl Sysvar for Rent { + impl_sysvar_get!(sol_get_rent_sysvar); +} diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index c7a64fdd54..481b5afb71 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -127,6 +127,10 @@ pub mod demote_sysvar_write_locks { solana_sdk::declare_id!("86LJYRuq2zgtHuL3FccR6hqFJQMQkFoun4knAxcPiF1P"); } +pub mod sysvar_via_syscall { + solana_sdk::declare_id!("7411E6gFQLDhQkdRjmpXwM1hzHMMoYQUjHicmvGPC1Nf"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -159,6 +163,7 @@ lazy_static! { (cpi_data_cost::id(), "charge the compute budget for data passed via CPI"), (upgradeable_close_instruction::id(), "close upgradeable buffer accounts"), (demote_sysvar_write_locks::id(), "demote builtins and sysvar write locks to readonly #15497"), + (sysvar_via_syscall::id(), "Provide sysvars via syscalls"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/process_instruction.rs b/sdk/src/process_instruction.rs index 72017813a2..3384a7267c 100644 --- a/sdk/src/process_instruction.rs +++ b/sdk/src/process_instruction.rs @@ -7,7 +7,7 @@ use solana_sdk::{ }; use std::{cell::RefCell, fmt::Debug, rc::Rc, sync::Arc}; -// Prototype of a native loader entry point +/// Prototype of a native loader entry point /// /// program_id: Program ID of the currently executing program /// keyed_accounts: Accounts passed as part of the instruction @@ -68,6 +68,8 @@ pub trait InvokeContext { execute_us: u64, deserialize_us: u64, ); + /// Get sysvar data + fn get_sysvar_data(&mut self, id: &Pubkey) -> Option>>; } /// Convenience macro to log a message with an `Rc>` @@ -130,6 +132,8 @@ pub struct BpfComputeBudget { pub max_cpi_instruction_size: usize, /// Number of account data bytes per conpute unit charged during a cross-program invocation pub cpi_bytes_per_unit: u64, + /// Base number of compute units consumed to get a sysvar + pub sysvar_base_cost: u64, } impl Default for BpfComputeBudget { fn default() -> Self { @@ -152,6 +156,7 @@ impl BpfComputeBudget { log_pubkey_units: 100, max_cpi_instruction_size: 1280, // IPv6 Min MTU size cpi_bytes_per_unit: 250, // ~50MB at 200,000 units + sysvar_base_cost: 100, } } } @@ -277,7 +282,9 @@ pub struct MockInvokeContext { pub bpf_compute_budget: BpfComputeBudget, pub compute_meter: MockComputeMeter, pub programs: Vec<(Pubkey, ProcessInstructionWithContext)>, + pub accounts: Vec<(Pubkey, Rc>)>, pub invoke_depth: usize, + pub sysvars: Vec<(Pubkey, Option>>)>, } impl Default for MockInvokeContext { fn default() -> Self { @@ -289,7 +296,9 @@ impl Default for MockInvokeContext { remaining: std::i64::MAX as u64, }, programs: vec![], + accounts: vec![], invoke_depth: 0, + sysvars: vec![], } } } @@ -336,7 +345,12 @@ impl InvokeContext for MockInvokeContext { fn is_feature_active(&self, _feature_id: &Pubkey) -> bool { true } - fn get_account(&self, _pubkey: &Pubkey) -> Option>> { + fn get_account(&self, pubkey: &Pubkey) -> Option>> { + for (key, account) in self.accounts.iter() { + if key == pubkey { + return Some(account.clone()); + } + } None } fn update_timing( @@ -347,4 +361,9 @@ impl InvokeContext for MockInvokeContext { _deserialize_us: u64, ) { } + fn get_sysvar_data(&mut self, id: &Pubkey) -> Option>> { + self.sysvars + .iter() + .find_map(|(key, sysvar)| if id == key { sysvar.clone() } else { None }) + } }