diff --git a/docs/src/developing/on-chain-programs/developing-c.md b/docs/src/developing/on-chain-programs/developing-c.md index 15197f511c..cffbd1006b 100644 --- a/docs/src/developing/on-chain-programs/developing-c.md +++ b/docs/src/developing/on-chain-programs/developing-c.md @@ -157,6 +157,9 @@ with program logs. ## Compute Budget +Use the system call `sol_remaining_compute_units()` to return a `u64` indicating +the number of compute units remaining for this transaction. + Use the system call [`sol_log_compute_units()`](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/sbf/c/inc/solana_sdk.h#L140) to log a message containing the remaining number of compute units the program diff --git a/docs/src/developing/on-chain-programs/developing-rust.md b/docs/src/developing/on-chain-programs/developing-rust.md index 423b214935..3e21799222 100644 --- a/docs/src/developing/on-chain-programs/developing-rust.md +++ b/docs/src/developing/on-chain-programs/developing-rust.md @@ -355,6 +355,9 @@ fn custom_panic(info: &core::panic::PanicInfo<'_>) { ## Compute Budget +Use the system call `sol_remaining_compute_units()` to return a `u64` indicating +the number of compute units remaining for this transaction. + Use the system call [`sol_log_compute_units()`](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/log.rs#L141) to log a message containing the remaining number of compute units the program diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs index 02983f9b9a..44fb070b37 100644 --- a/program-runtime/src/compute_budget.rs +++ b/program-runtime/src/compute_budget.rs @@ -130,6 +130,8 @@ pub struct ComputeBudget { /// of compute units consumed to call poseidon syscall for a given number /// of inputs. pub poseidon_cost_coefficient_c: u64, + /// Number of compute units consumed for accessing the remaining compute units. + pub get_remaining_compute_units_cost: u64, } impl Default for ComputeBudget { @@ -181,6 +183,7 @@ impl ComputeBudget { loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, poseidon_cost_coefficient_a: 61, poseidon_cost_coefficient_c: 542, + get_remaining_compute_units_cost: 100, } } diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index eb31edf3cd..e8a0b70bc1 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -40,8 +40,8 @@ use { enable_early_verification_of_account_modifications, enable_partitioned_epoch_reward, enable_poseidon_syscall, 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, + remaining_compute_units_syscall_enabled, stop_sibling_instruction_search_at_parent, + stop_truncating_strings_in_syscalls, switch_to_new_elf_parser, }, hash::{Hasher, HASH_BYTES}, instruction::{ @@ -164,6 +164,8 @@ pub fn create_program_runtime_environment_v1<'a>( && 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 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()); // !!! ATTENTION !!! // When adding new features for RBPF here, // also add them to `Bank::apply_builtin_program_feature_transitions()`. @@ -335,6 +337,14 @@ pub fn create_program_runtime_environment_v1<'a>( SyscallPoseidon::call, )?; + // Accessing remaining compute units + register_feature_gated_function!( + result, + remaining_compute_units_syscall_enabled, + *b"sol_remaining_compute_units", + SyscallRemainingComputeUnits::call + )?; + // Log data result.register_function_hashed(*b"sol_log_data", SyscallLogData::call)?; @@ -1877,6 +1887,26 @@ declare_syscall!( } ); +declare_syscall!( + /// Read remaining compute units + SyscallRemainingComputeUnits, + fn inner_call( + invoke_context: &mut InvokeContext, + _arg1: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + _memory_mapping: &mut MemoryMapping, + ) -> Result { + let budget = invoke_context.get_compute_budget(); + consume_compute_meter(invoke_context, budget.syscall_base_cost)?; + + use solana_rbpf::vm::ContextObject; + Ok(invoke_context.get_remaining()) + } +); + #[cfg(test)] #[allow(clippy::arithmetic_side_effects)] #[allow(clippy::indexing_slicing)] diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index ffd939ff5f..2dc3879a46 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5889,6 +5889,16 @@ dependencies = [ "solana-sbf-rust-realloc", ] +[[package]] +name = "solana-sbf-rust-remaining-compute-units" +version = "1.17.0" +dependencies = [ + "solana-program", + "solana-program-runtime", + "solana-program-test", + "solana-sdk", +] + [[package]] name = "solana-sbf-rust-ro-account_modify" version = "1.17.0" diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 2ef02e54b8..363a3a4972 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -145,6 +145,7 @@ members = [ "rust/rand", "rust/realloc", "rust/realloc_invoke", + "rust/remaining_compute_units", "rust/ro_account_modify", "rust/ro_modify", "rust/sanity", diff --git a/programs/sbf/build.rs b/programs/sbf/build.rs index 8bffd48c9f..6bdfb9a4ea 100644 --- a/programs/sbf/build.rs +++ b/programs/sbf/build.rs @@ -96,6 +96,7 @@ fn main() { "rand", "realloc", "realloc_invoke", + "remaining_compute_units", "ro_modify", "ro_account_modify", "sanity", diff --git a/programs/sbf/c/src/remaining_compute_units/remaining_compute_units.c b/programs/sbf/c/src/remaining_compute_units/remaining_compute_units.c new file mode 100644 index 0000000000..514529b909 --- /dev/null +++ b/programs/sbf/c/src/remaining_compute_units/remaining_compute_units.c @@ -0,0 +1,23 @@ +/** + * @brief sol_remaining_compute_units Syscall test + */ +#include +#include + +extern uint64_t entrypoint(const uint8_t *input) { + char buffer[200]; + + int i = 0; + for (; i < 100000; ++i) { + if (i % 500 == 0) { + uint64_t remaining = sol_remaining_compute_units(); + snprintf(buffer, 200, "remaining compute units: %d", (int)remaining); + sol_log(buffer); + if (remaining < 25000) { + break; + } + } + } + + return SUCCESS; +} diff --git a/programs/sbf/rust/remaining_compute_units/Cargo.toml b/programs/sbf/rust/remaining_compute_units/Cargo.toml new file mode 100644 index 0000000000..223679c072 --- /dev/null +++ b/programs/sbf/rust/remaining_compute_units/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "solana-sbf-rust-remaining-compute-units" +documentation = "https://docs.rs/solana-sbf-rust-remaining-compute-units" +version = { workspace = true } +description = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[features] +no-entrypoint = [] +test-bpf = [] +dummy-for-ci-check = ["test-bpf"] + +[dependencies] +solana-program = { workspace = true } + +[dev-dependencies] +solana-program-runtime = { workspace = true } +solana-program-test = { workspace = true } +solana-sdk = { workspace = true } + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/sbf/rust/remaining_compute_units/src/lib.rs b/programs/sbf/rust/remaining_compute_units/src/lib.rs new file mode 100644 index 0000000000..ecf0376397 --- /dev/null +++ b/programs/sbf/rust/remaining_compute_units/src/lib.rs @@ -0,0 +1,29 @@ +//! @brief Example Rust-based BPF program that exercises the sol_remaining_compute_units syscall + +extern crate solana_program; +use solana_program::{ + account_info::AccountInfo, compute_units::sol_remaining_compute_units, + entrypoint::ProgramResult, msg, pubkey::Pubkey, +}; +solana_program::entrypoint!(process_instruction); +pub fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + let mut i = 0u32; + for _ in 0..100_000 { + if i % 500 == 0 { + let remaining = sol_remaining_compute_units(); + msg!("remaining compute units: {:?}", remaining); + if remaining < 25_000 { + break; + } + } + i = i.saturating_add(1); + } + + msg!("i: {:?}", i); + + Ok(()) +} diff --git a/programs/sbf/rust/remaining_compute_units/tests/lib.rs b/programs/sbf/rust/remaining_compute_units/tests/lib.rs new file mode 100644 index 0000000000..30da15b295 --- /dev/null +++ b/programs/sbf/rust/remaining_compute_units/tests/lib.rs @@ -0,0 +1,27 @@ +#![cfg(feature = "test-bpf")] + +use { + solana_program_test::*, + solana_sbf_rust_remaining_compute_units::process_instruction, + solana_sdk::{ + instruction::Instruction, pubkey::Pubkey, signature::Signer, transaction::Transaction, + }, +}; + +#[tokio::test] +async fn test_remaining_compute_units() { + let program_id = Pubkey::new_unique(); + let program_test = ProgramTest::new( + "solana_sbf_rust_remaining_compute_units", + program_id, + processor!(process_instruction), + ); + let (mut banks_client, payer, recent_blockhash) = program_test.start().await; + + let mut transaction = Transaction::new_with_payer( + &[Instruction::new_with_bincode(program_id, &(), vec![])], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); +} diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 65546bb205..e110778a9e 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -8111,6 +8111,7 @@ impl Bank { feature_set::disable_deploy_of_alloc_free_syscall::id(), feature_set::last_restart_slot_sysvar::id(), feature_set::delay_visibility_of_program_deployment::id(), + feature_set::remaining_compute_units_syscall_enabled::id(), ]; if !only_apply_transitions_for_new_features || FEATURES_AFFECTING_RBPF diff --git a/sdk/program/src/compute_units.rs b/sdk/program/src/compute_units.rs new file mode 100644 index 0000000000..6b7f271271 --- /dev/null +++ b/sdk/program/src/compute_units.rs @@ -0,0 +1,13 @@ +/// Return the remaining compute units the program may consume +#[inline] +pub fn sol_remaining_compute_units() -> u64 { + #[cfg(target_os = "solana")] + unsafe { + crate::syscalls::sol_remaining_compute_units() + } + + #[cfg(not(target_os = "solana"))] + { + crate::program_stubs::sol_remaining_compute_units() + } +} diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 63da3fa7f1..edcc2e3cb8 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -483,6 +483,7 @@ pub mod bpf_loader; pub mod bpf_loader_deprecated; pub mod bpf_loader_upgradeable; pub mod clock; +pub mod compute_units; pub mod debug_account_data; pub mod decode_error; pub mod ed25519_program; diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index 24f4dc57d3..cf890659fa 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -30,6 +30,10 @@ pub trait SyscallStubs: Sync + Send { fn sol_log_compute_units(&self) { sol_log("SyscallStubs: sol_log_compute_units() not available"); } + fn sol_remaining_compute_units(&self) -> u64 { + sol_log("SyscallStubs: sol_remaining_compute_units() defaulting to 0"); + 0 + } fn sol_invoke_signed( &self, _instruction: &Instruction, @@ -126,6 +130,10 @@ pub(crate) fn sol_log_compute_units() { SYSCALL_STUBS.read().unwrap().sol_log_compute_units(); } +pub(crate) fn sol_remaining_compute_units() -> u64 { + SYSCALL_STUBS.read().unwrap().sol_remaining_compute_units() +} + pub(crate) fn sol_invoke_signed( instruction: &Instruction, account_infos: &[AccountInfo], diff --git a/sdk/program/src/syscalls/definitions.rs b/sdk/program/src/syscalls/definitions.rs index dbf6d5e3a4..c3aa74ff8d 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -70,6 +70,7 @@ define_syscall!(fn sol_alt_bn128_group_op(group_op: u64, input: *const u8, input define_syscall!(fn sol_big_mod_exp(params: *const u8, result: *mut u8) -> u64); 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); #[cfg(target_feature = "static-syscalls")] pub const fn sys_hash(name: &str) -> usize { diff --git a/sdk/sbf/c/inc/sol/compute_units.h b/sdk/sbf/c/inc/sol/compute_units.h new file mode 100644 index 0000000000..a4a9f40bf4 --- /dev/null +++ b/sdk/sbf/c/inc/sol/compute_units.h @@ -0,0 +1,42 @@ +#pragma once +/** + * @brief Solana logging utilities + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Prints a string to stdout + */ +/* DO NOT MODIFY THIS GENERATED FILE. INSTEAD CHANGE sdk/sbf/c/inc/sol/inc/compute_units.inc AND RUN `cargo run --bin gen-headers` */ +#ifndef SOL_SBFV2 +uint64_t sol_remaining_compute_units(); +#else +typedef uint64_t(*sol_remaining_compute_units_pointer_type)(); +static uint64_t sol_remaining_compute_units() { + sol_remaining_compute_units_pointer_type sol_remaining_compute_units_pointer = (sol_remaining_compute_units_pointer_type) 3991886574; + return sol_remaining_compute_units_pointer(); +} +#endif + +#ifdef SOL_TEST +/** + * Stub functions when building tests + */ + +uint64_t sol_remaining_compute_units() { + return UINT64_MAX; +} +#endif + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/sbf/c/inc/sol/inc/compute_units.inc b/sdk/sbf/c/inc/sol/inc/compute_units.inc new file mode 100644 index 0000000000..4d13ac94f3 --- /dev/null +++ b/sdk/sbf/c/inc/sol/inc/compute_units.inc @@ -0,0 +1,33 @@ +#pragma once +/** + * @brief Solana logging utilities + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Prints a string to stdout + */ +@SYSCALL uint64_t sol_remaining_compute_units(); + +#ifdef SOL_TEST +/** + * Stub functions when building tests + */ + +uint64_t sol_remaining_compute_units() { + return UINT64_MAX; +} +#endif + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/sbf/c/inc/solana_sdk.h b/sdk/sbf/c/inc/solana_sdk.h index 5c8e437018..7a3b49d5ec 100644 --- a/sdk/sbf/c/inc/solana_sdk.h +++ b/sdk/sbf/c/inc/solana_sdk.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 2ce873fe5f..418d328748 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -691,6 +691,10 @@ pub mod timely_vote_credits { solana_sdk::declare_id!("2oXpeh141pPZCTCFHBsvCwG2BtaHZZAtrVhwaxSy6brS"); } +pub mod remaining_compute_units_syscall_enabled { + solana_sdk::declare_id!("5TuppMutoyzhUSfuYdhgzD47F92GL1g89KpCZQKqedxP"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -856,6 +860,7 @@ lazy_static! { (revise_turbine_epoch_stakes::id(), "revise turbine epoch stakes"), (enable_poseidon_syscall::id(), "Enable Poseidon syscall"), (timely_vote_credits::id(), "use timeliness of votes in determining credits to award"), + (remaining_compute_units_syscall_enabled::id(), "enable the remaining_compute_units syscall"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()