Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ edition = "2021"

[workspace.lints.rust]
warnings = "deny"
mismatched_lifetime_syntaxes = "allow"

[workspace.lints.rust.unexpected_cfgs]
level = "warn"
Expand Down Expand Up @@ -644,6 +645,8 @@ crossbeam-epoch = { git = "https://github.com/anza-xyz/crossbeam", rev = "fd279d
# for details, see https://github.com/anza-xyz/agave/issues/8262
quinn = { git = "https://github.com/anza-xyz/quinn", rev = "fc4decb0cf79b1b210603294e96849d67e9c22e2" }
quinn-proto = { git = "https://github.com/anza-xyz/quinn", rev = "fc4decb0cf79b1b210603294e96849d67e9c22e2" }
# Patched sbpf with 32-bit host address support for RISC0 zkVM
solana-sbpf = { path = "../sbpf" }

# We include the following crates as our dependencies above from crates.io:
#
Expand Down
1 change: 1 addition & 0 deletions program-runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
#![deny(clippy::arithmetic_side_effects)]
#![deny(clippy::indexing_slicing)]
#![allow(deprecated)]

pub use solana_sbpf;
pub mod execution_budget;
Expand Down
1 change: 1 addition & 0 deletions programs/stake/src/epoch_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use {
},
};

#[allow(deprecated)]
pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 {
let data = vec![0; EpochRewards::size_of()];
let lamports = std::cmp::max(genesis_config.rent.minimum_balance(data.len()), 1);
Expand Down
11 changes: 11 additions & 0 deletions svm-measure/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ macro_rules! measure_us {
// The macro name, `meas_dur`, is "measure" + "duration".
// When said aloud, the pronunciation is close to "measure".
#[macro_export]
#[cfg(not(target_os = "zkvm"))]
macro_rules! meas_dur {
($expr:expr) => {{
let start = std::time::Instant::now();
Expand All @@ -114,6 +115,16 @@ macro_rules! meas_dur {
}};
}

// On zkVM, time is not available, so return zero duration
#[macro_export]
#[cfg(target_os = "zkvm")]
macro_rules! meas_dur {
($expr:expr) => {{
let result = $expr;
(result, std::time::Duration::ZERO)
}};
}

#[cfg(test)]
mod tests {
use std::{thread::sleep, time::Duration};
Expand Down
71 changes: 67 additions & 4 deletions svm-measure/src/measure.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,78 @@
use std::{
fmt,
time::{Duration, Instant},
};
use std::{fmt, time::Duration};

// On zkVM targets, we can't use Instant::now() because time is not available.
// We provide a no-op implementation that doesn't track real time.
#[cfg(target_os = "zkvm")]
#[derive(Debug)]
pub struct Measure {
name: &'static str,
duration: u64,
}

#[cfg(target_os = "zkvm")]
impl Measure {
pub fn start(name: &'static str) -> Self {
Self { name, duration: 0 }
}

pub fn stop(&mut self) {
// No-op on zkVM - we can't measure time
}

pub fn as_ns(&self) -> u64 {
self.duration
}

pub fn as_us(&self) -> u64 {
self.duration / 1000
}

pub fn as_ms(&self) -> u64 {
self.duration / (1000 * 1000)
}

pub fn as_s(&self) -> f32 {
self.duration as f32 / (1000.0f32 * 1000.0f32 * 1000.0f32)
}

pub fn as_duration(&self) -> Duration {
Duration::from_nanos(self.as_ns())
}

pub fn end_as_ns(self) -> u64 {
0
}

pub fn end_as_us(self) -> u64 {
0
}

pub fn end_as_ms(self) -> u64 {
0
}

pub fn end_as_s(self) -> f32 {
0.0
}

pub fn end_as_duration(self) -> Duration {
Duration::ZERO
}
}

// On non-zkVM targets, use the real Instant-based implementation
#[cfg(not(target_os = "zkvm"))]
use std::time::Instant;

#[cfg(not(target_os = "zkvm"))]
#[derive(Debug)]
pub struct Measure {
name: &'static str,
start: Instant,
duration: u64,
}

#[cfg(not(target_os = "zkvm"))]
impl Measure {
pub fn start(name: &'static str) -> Self {
Self {
Expand Down
2 changes: 2 additions & 0 deletions svm-transaction/src/svm_message.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(mismatched_lifetime_syntaxes)]

use {
crate::{
instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup,
Expand Down
2 changes: 2 additions & 0 deletions svm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
#![allow(clippy::arithmetic_side_effects)]
#![allow(unknown_lints)]
#![allow(mismatched_lifetime_syntaxes)]

pub mod account_loader;
pub mod account_overrides;
Expand Down
162 changes: 160 additions & 2 deletions syscalls/src/cpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ fn translate_slice_mut<'a, T>(
)
}

/// Returns the address of `T` of `Rc<RefCell<T>>`
fn rc_refcell_content_addr(rc_refcell_addr: u64) -> u64 {
// Rc<RefCell<T>> layout: Rc.strong (8) + Rc.weak (8) + RefCell.borrow (8) = 24 bytes
rc_refcell_addr.saturating_add(24)
}

/// Host side representation of AccountInfo or SolAccountInfo passed to the CPI syscall.
///
/// At the start of a CPI, this can be different from the data stored in the
Expand Down Expand Up @@ -123,6 +129,9 @@ impl<'a> CallerAccount<'a> {
}

// Create a CallerAccount given an AccountInfo.
// Note: This is kept for tests but production code uses from_vm_account_info
// for 32-bit host compatibility.
#[allow(dead_code)]
fn from_account_info(
invoke_context: &InvokeContext,
memory_mapping: &MemoryMapping<'_>,
Expand Down Expand Up @@ -241,6 +250,128 @@ impl<'a> CallerAccount<'a> {
})
}

// Create a CallerAccount given a VmAccountInfo (for Rust CPI with 32-bit host compatibility).
//
// This is a modification of `from_account_info` but uses `VmAccountInfo` for 32-bit host compatibility.
fn from_vm_account_info(
invoke_context: &InvokeContext,
memory_mapping: &MemoryMapping<'_>,
check_aligned: bool,
_vm_addr: u64,
account_info: &VmAccountInfo,
account_metadata: &SerializedAccountMetadata,
) -> Result<CallerAccount<'a>, Error> {
let stricter_abi_and_runtime_constraints = invoke_context
.get_feature_set()
.stricter_abi_and_runtime_constraints;

if stricter_abi_and_runtime_constraints {
check_account_info_pointer(
invoke_context,
account_info.key_addr,
account_metadata.vm_key_addr,
"key",
)?;
check_account_info_pointer(
invoke_context,
account_info.owner_addr,
account_metadata.vm_owner_addr,
"owner",
)?;
}

// account_info points to host memory. The addresses used internally are
// in vm space so they need to be translated.
let lamports = {
// Double translate lamports out of RefCell
let ptr = translate_type::<u64>(
memory_mapping,
rc_refcell_content_addr(account_info.lamports_cell_addr),
check_aligned,
)?;
if stricter_abi_and_runtime_constraints {
if account_info.lamports_cell_addr >= ebpf::MM_INPUT_START {
return Err(SyscallError::InvalidPointer.into());
}
check_account_info_pointer(
invoke_context,
*ptr,
account_metadata.vm_lamports_addr,
"lamports",
)?;
}
translate_type_mut::<u64>(memory_mapping, *ptr, check_aligned)?
};

let owner = translate_type_mut::<Pubkey>(
memory_mapping,
account_info.owner_addr,
check_aligned,
)?;

let (serialized_data, vm_data_addr, ref_to_len_in_vm) = {
if stricter_abi_and_runtime_constraints
&& account_info.data_cell_addr >= ebpf::MM_INPUT_START
{
return Err(SyscallError::InvalidPointer.into());
}

// Double translate data out of RefCell.
// Use `VmSlice<u8>` instead of `&[u8]` for 32bits compatibility.
let data_slice = translate_type::<VmSlice<u8>>(
memory_mapping,
rc_refcell_content_addr(account_info.data_cell_addr),
check_aligned,
)?;
if stricter_abi_and_runtime_constraints {
check_account_info_pointer(
invoke_context,
data_slice.ptr(),
account_metadata.vm_data_addr,
"data",
)?;
}

consume_compute_meter(
invoke_context,
data_slice
.len()
.checked_div(invoke_context.get_execution_cost().cpi_bytes_per_unit)
.unwrap_or(u64::MAX),
)?;

let vm_len_addr = rc_refcell_content_addr(account_info.data_cell_addr)
.saturating_add(size_of::<u64>() as u64);
if stricter_abi_and_runtime_constraints {
// In the same vein as the other check_account_info_pointer() checks, we don't lock
// this pointer to a specific address but we don't want it to be inside accounts, or
// callees might be able to write to the pointed memory.
if vm_len_addr >= ebpf::MM_INPUT_START {
return Err(SyscallError::InvalidPointer.into());
}
}
let ref_to_len_in_vm = translate_type_mut::<u64>(memory_mapping, vm_len_addr, false)?;
let vm_data_addr = data_slice.ptr();
let serialized_data = CallerAccount::get_serialized_data(
memory_mapping,
vm_data_addr,
data_slice.len(),
stricter_abi_and_runtime_constraints,
invoke_context.account_data_direct_mapping,
)?;
(serialized_data, vm_data_addr, ref_to_len_in_vm)
};

Ok(CallerAccount {
lamports,
owner,
original_data_len: account_metadata.original_data_len,
serialized_data,
vm_data_addr,
ref_to_len_in_vm,
})
}

// Create a CallerAccount given a SolAccountInfo.
fn from_sol_account_info(
invoke_context: &InvokeContext,
Expand Down Expand Up @@ -442,10 +573,13 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
invoke_context: &mut InvokeContext,
check_aligned: bool,
) -> Result<Vec<TranslatedAccount<'a>>, Error> {
// Use VmAccountInfo instead of AccountInfo for 32-bit host compatibility.
// The BPF VM is always 64-bit, so AccountInfo in VM memory uses 64-bit pointers.
// VmAccountInfo has explicit u64 fields to correctly read the 64-bit layout.
let (account_infos, account_info_keys) = translate_account_infos(
account_infos_addr,
account_infos_len,
|account_info: &AccountInfo| account_info.key as *const _ as u64,
|account_info: &VmAccountInfo| account_info.key_addr,
memory_mapping,
invoke_context,
check_aligned,
Expand All @@ -458,7 +592,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
invoke_context,
memory_mapping,
check_aligned,
CallerAccount::from_account_info,
CallerAccount::from_vm_account_info,
)
}

Expand Down Expand Up @@ -542,6 +676,30 @@ struct SolAccountInfo {
executable: bool,
}

/// Rust representation of `AccountInfo` in VM memory.
/// This struct has explicit u64 fields for 32-bit host compatibility.
/// The layout matches the 64-bit BPF VM's AccountInfo layout (#[repr(C)]).
#[derive(Debug)]
#[repr(C)]
struct VmAccountInfo {
/// VM address pointing to the key (Pubkey)
key_addr: u64,
/// VM address of the Rc<RefCell<&mut u64>> pointer for lamports
lamports_cell_addr: u64,
/// VM address of the Rc<RefCell<&mut [u8]>> pointer for data
data_cell_addr: u64,
/// VM address pointing to the owner (Pubkey)
owner_addr: u64,
/// Unused field (formerly rent_epoch), preserved for ABI compatibility
_unused: u64,
/// Was the transaction signed by this account's public key?
is_signer: bool,
/// Is the account writable?
is_writable: bool,
/// This account's data contains a loaded program (and is now read-only)
executable: bool,
}

/// Rust representation of C's SolSignerSeed
#[derive(Debug)]
#[repr(C)]
Expand Down
Loading