Skip to main content

Crate hpsvm

Crate hpsvm 

Source
Expand description

Fast, lightweight, in-process Solana VM for testing programs.

hpsvm executes Solana transactions in-process against a single mutable environment: fund accounts, stage transactions, then keep or roll back their post-state. Hpsvm is the ready-made instantiation (the base type family); Svm is the generic host behind it, parameterized by a SvmTypesHost family.

§Minimal example

use hpsvm::Hpsvm;
use solana_address::Address;
use solana_keypair::Keypair;
use solana_message::Message;
use solana_signer::Signer;
use solana_system_interface::instruction::transfer;
use solana_transaction::Transaction;

let from_keypair = Keypair::new();
let from = from_keypair.pubkey();
let to = Address::new_unique();

let mut svm = Hpsvm::new();
svm.airdrop(&from, 1_000_000_000).unwrap();
svm.airdrop(&to, 1_000_000_000).unwrap();

let instruction = transfer(&from, &to, 64);
let tx = Transaction::new(
    &[&from_keypair],
    Message::new(&[instruction], Some(&from)),
    svm.latest_blockhash(),
);
svm.transact(tx).expect("transact should stage").commit().unwrap();

assert_eq!(svm.get_balance(&from).unwrap(), 999_994_936);
assert_eq!(svm.get_balance(&to).unwrap(), 1_000_000_064);

// A second transfer can be staged and then rolled back.
let retry = transfer(&from, &to, 100);
let tx2 = Transaction::new(
    &[&from_keypair],
    Message::new(&[retry], Some(&from)),
    svm.latest_blockhash(),
);
svm.transact(tx2).expect("transact should stage").discard().unwrap();
assert_eq!(svm.get_balance(&from).unwrap(), 999_994_936);
assert_eq!(svm.get_balance(&to).unwrap(), 1_000_000_064);

§Architecture

§Type family

Svm is generic over a SvmTypesHost family selecting the spec id, transaction envelope, host, and extension slots. BaseSvmTypes is the stock family and Hpsvm names Svm<'static, BaseSvmTypes>. A custom family implements SvmTypesHost (plus SvmTypes for the defaulted slots) and runs anywhere the base family does.

§Execution protocol

Version is the execution-shaping protocol configuration: feature set, signature verification, blockhash checks, compute budget, rent, limits, and the ClusterSpecId profile. Inspect it with Svm::version; change it with the set_* reconfigurers (Svm::set_feature_set, Svm::set_sigverify, Svm::set_compute_budget). Host is the execution-facing view of the VM that every handler receives.

§Staged execution

Svm::transact executes and stages the post-state in the live overlay, returning an ExecutedTx that borrows the VM. ExecutedTx::commit keeps the staged state and records history, ExecutedTx::discard rolls it back, and ExecutedTx::commit_with streams each account change into a StateChangeSink first. Svm::try_transact and Svm::transact_detached return the owned readonly DetachedTx without staging; Svm::send_transaction is transact plus commit in one call.

§Transaction registry

Every entry point dispatches through a TxRegistry keyed by TxTag. Svm::register_tx_handler replaces the TxHandler for one tag, which is how tests and forks intercept execution; the handler helper builds a handler from a validate/execute pair.

§Precompiles

A PrecompileProvider answers which program ids run as precompiles. PrecompileSet is the map-backed provider, NoPrecompiles disables dispatch, and Svm::set_precompile_provider swaps the active provider (plus Precompile for a single entry).

§Inspectors

An Inspector observes execution through &mut self hooks. Install one with Svm::set_inspector (or Svm::with_inspector) and recover it with Svm::clear_inspector. OriginFiltered scopes hooks by TransactionOrigin via OriginFilter; InspectMode is the const-generic switch that compiles hook dispatch away when no inspector is live.

§Declaring instructions

hpsvm_macros::svm_instruction declares a builtin instruction from a function, threaded through SvmInstructionCtx with declared SvmInstructionAccounts (see examples/custom_svm.rs below).

§Errors

Setup and fatal staging failures surface as HPSVMError; per-transaction outcomes are TransactionResults carrying TransactionMetadata or FailedTransactionMetadata, with ExecutionDiagnostics for pre/post diffs. Handler failures use HandlerError, registry misuse TxRegistryError, validation TxValidationError, and host violations HostError.

§State model

Staging journals overlay mutations behind an internal checkpoint (rolled back by discard); caches and history derive from the overlay. The checkpoint type and the account database are crate-internal; the public state views are AccountsView and StateChangeSet.

§Deploying programs

Beyond transfers, add compiled programs with Hpsvm::add_program (or Hpsvm::add_program_from_file) and exercise them through staged or direct transactions. Pull released programs with solana program dump.

use hpsvm::Hpsvm;
use solana_address::{Address, address};
use solana_instruction::{Instruction, account_meta::AccountMeta};
use solana_keypair::Keypair;
use solana_message::{Message, VersionedMessage};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;

fn test_logging() {
    let program_id = address!("Logging111111111111111111111111111111111111");
    let account_meta =
        AccountMeta { pubkey: Address::new_unique(), is_signer: false, is_writable: true };
    let ix = Instruction {
        program_id,
        accounts: vec![account_meta],
        data: vec![5, 10, 11, 12, 13, 14],
    };
    let mut svm = Hpsvm::new();
    let payer = Keypair::new();
    // The `counter` test program is a separate SBF build; skip when absent.
    let Some(bytes) = hpsvm_test_support::read_program(hpsvm_test_support::COUNTER) else {
        return;
    };
    svm.add_program(program_id, &bytes[..]);
    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
    let blockhash = svm.latest_blockhash();
    let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash);
    let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();
    // Let's simulate it first
    let sim_res = svm.simulate_transaction(tx.clone()).unwrap();
    let meta = svm.send_transaction(tx).unwrap();
    assert_eq!(sim_res.meta, meta);
    // The program should log something
    assert!(meta.logs.len() > 1);
    assert!(meta.compute_units_consumed < 10_000); // not being precise here in case it changes
}

§Compiled-program tiering

Program compilation is tiered, cached and observable — the harness around agave’s solana-sbpf JIT, not a fork of it:

  • JitPolicy on Version selects the tier (Eager compiles every program at load time on x86_64; Lazy interprets until a program crosses its hot threshold, then promotes it on a background pool; Off forces the interpreter even on x86_64). The default is Eager, byte-identical to the pre-tiering implementation.
  • The resident entry store (content-keyed by environment identity, ELF hash, loader and slot bucket) re-links already-compiled entries across VMs and across rebuild_program_cache instead of recompiling them.
  • Svm::prewarm_programs front-loads a batch stage’s program set before its first transaction (and schedules promotion under Lazy).
  • Svm::program_tier_stats reports policy, resident/cache entries, agave compilations and invocation splits, plus promotion outcomes — all from atomics, with Off as the effective policy off x86_64.
use hpsvm::{Hpsvm, JitPolicy};

let mut svm = Hpsvm::new().with_jit_policy(JitPolicy::Off);
assert_eq!(svm.version().jit_policy, JitPolicy::Off);
svm.prewarm_programs(&[]).expect("empty prewarm is a no-op");
let stats = svm.program_tier_stats();
// `Off` is effective on every target, so this doctest holds on x86_64
// and elsewhere alike.
assert_eq!(stats.effective_policy, JitPolicy::Off);
assert_eq!(stats.promotions_attempted, 0);

§Time travel

Programs reading the Clock sysvar can be tested across time with Hpsvm::set_sysvar / Hpsvm::get_sysvar, or jumped across slots with Hpsvm::warp_to_slot. Here’s an example using a program that panics if clock.unix_timestamp is greater than 100 (January 1st 1970):

use hpsvm::Hpsvm;
use solana_address::Address;
use solana_clock::Clock;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_message::{Message, VersionedMessage};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;

fn test_set_clock() {
    let program_id = Address::new_unique();
    let mut svm = Hpsvm::new();
    // The clock example is a separate SBF build; skip when absent.
    let Some(bytes) = hpsvm_test_support::read_program(hpsvm_test_support::CLOCK_EXAMPLE)
    else {
        return;
    };
    svm.add_program(program_id, &bytes[..]);
    let payer = Keypair::new();
    let payer_address = payer.pubkey();
    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
    let blockhash = svm.latest_blockhash();
    let ixs = [Instruction { program_id, data: vec![], accounts: vec![] }];
    let msg = Message::new_with_blockhash(&ixs, Some(&payer_address), &blockhash);
    let versioned_msg = VersionedMessage::Legacy(msg);
    let tx = VersionedTransaction::try_new(versioned_msg, &[&payer]).unwrap();
    // Set the time to January 1st 2000
    let mut initial_clock = svm.get_sysvar::<Clock>();
    initial_clock.unix_timestamp = 1735689600;
    svm.set_sysvar::<Clock>(&initial_clock).expect("clock sysvar override should succeed");
    // This will fail because the program expects early 1970 timestamp
    let _err = svm.send_transaction(tx.clone()).unwrap_err();
    // So let's turn back time
    let mut clock = svm.get_sysvar::<Clock>();
    clock.unix_timestamp = 50;
    svm.set_sysvar::<Clock>(&clock).expect("clock sysvar override should succeed");
    let ixs2 = [Instruction {
        program_id,
        data: vec![1], // unused, this is just to dedup the transaction
        accounts: vec![],
    }];
    let msg2 = Message::new_with_blockhash(&ixs2, Some(&payer_address), &blockhash);
    let versioned_msg2 = VersionedMessage::Legacy(msg2);
    let tx2 = VersionedTransaction::try_new(versioned_msg2, &[&payer]).unwrap();
    // Now the transaction goes through
    svm.send_transaction(tx2).unwrap();
}

§Custom environments

examples/custom_svm.rs stands up a custom SvmTypesHost family end to end: custom spec id, block-environment extension, transaction handler, macro-declared instruction, and custom precompile, each asserted observable through commit / commit_with flows. Start there when extending the VM.

§Migrating from the pre-refactor API

The refactor replaces the previous surface outright:

Re-exports§

pub use builder::HpsvmBuilder;
pub use error::AnyError;
pub use error::DbError;
pub use error::DbResult;
pub use error::ExecutionError;
pub use error::HaltReason;
pub use error::HandlerError;
pub use error::HostError;
pub use error::LoadError;
pub use error::TxError;
pub use error::TxValidationError;
pub use executed::DetachedTx;
pub use executed::ExecutedTx;
pub use state_change_sink::AccountChange;
pub use state_change_sink::AccountChangeRecord;
pub use state_change_sink::StateChangeSink;

Modules§

batch
Transaction batch planning and parallel execution.
error
Error types for the SVM.
executed
Staged (ExecutedTx) and detached (DetachedTx) transaction execution. Staged transaction execution: ExecutedTx and DetachedTx.
forkfork
RPC-backed account source for forking live cluster state. RPC-backed account source for forking live cluster state.
instruction
Solana instruction types and helpers.
loaderloader
Loader v3 helpers (set_upgrade_authority, deploy_upgradeable_program). Loader operations for Svm.
register_tracingregister-tracing
state_change_sink
State change sink: lets tooling observe account changes as they commit, without a second pass over live state.
tokentoken
SPL token operation helpers (transfer, mint, ATA, freeze, etc.). Token operations for Svm.
types
Public return types and execution metadata.

Structs§

AccountSourceError
AccountsView
Read-only facade over the VM accounts database.
BaseSvmTypes
The base type family: every extension slot is the unit type, except the spec identifier, which is the ClusterSpecId the VM’s Version tracks.
BlockEnv
Block-scoped runtime state exposed by the VM.
ClusterInfo
Identity of the cluster profile a Version was derived from.
DefaultExecutionHandler
The default execution handler: the pre-registry readonly execution path.
EmptyInvocationInspectCallbackinvocation-inspect-callback
HpsvmBuilder
Typed builder for Hpsvm.
NativePrecompilesprecompiles
The agave-backed default set: today’s precompile behaviour as a provider.
NoPrecompiles
The empty provider: no program id dispatches as a precompile.
OriginFiltered
Origin-aware decorator for an Inspector.
PrecompileSet
The active precompile set: program id to handler.
ProgramTierStats
Observable compiled-tier execution state.
RuntimeLimits
Numeric runtime limits shaping execution.
StateChangeSet
An ordered slice of journal mutations produced by one transaction execution (typically a parallel batch worker).
Svm
The core Solana Virtual Machine simulator, parameterized by its type family.
SvmInstructionAccount
One instruction account, as an owned snapshot.
SvmInstructionCtx
Owned handler context for one builtin-instruction invocation.
TxEnv
Transaction-scoped environment.
TxFeatureFlags
Feature-gate decisions for one transaction, resolved once via TxFeatureFlags::load.
TxRegistry
Tag-to-handler map consulted by every transaction entry point.
Version
All execution-shaping configuration in one value.

Enums§

ClusterSpecId
Built-in cluster profiles, each indexing one Version::base entry.
FeatureGate
Static feature gates resolved once per transaction into TxFeatureFlags.
InspectMode
Which execution path the VM monomorphizes for inspector dispatch.
JitPolicy
Compiled-program tiering policy.
OriginFilter
Which transaction origins an OriginFiltered decorator forwards.
TransactionOrigin
Origin of a transaction observed by an Inspector.
TxRegistryError
Dispatch failure: either no handler owns the tag, or the owning handler declined the transaction.
TxTag
Classifies a transaction envelope for handler dispatch.

Traits§

AccountSource
Host
Host capabilities a type family runs against.
Inspector
Observes transaction execution and accumulates inspection state.
InvocationInspectCallbackinvocation-inspect-callback
Low-level execution hook, parameterized by the VM’s type family.
Precompile
A single precompile entry: the program id it handles plus its verifier.
PrecompileProvider
Per-instance precompile dispatch, queried by program id.
SvmTypes
Blanket marker for a family whose host is the VM itself.
SvmTypesHost
One associated-type family describing every swappable axis of the VM.
TxHandler
A typed transaction handler: validation plus execution against the host.

Functions§

handler
Adapts a validate closure and an execute closure into one handler.
tag_of
Derives the dispatch tag for a transaction envelope.

Type Aliases§

HPSVM
The pre-refactor name for Svm<'static, BaseSvmTypes>.
Hpsvm
Ergonomic name for the base VM instantiation.