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
49 changes: 28 additions & 21 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
[workspace]
members = [
"solana/account",
"solana/program-runtime",
"solana/transaction-context",
]
members = ["solana/account", "solana/program-runtime", "solana/svm", "solana/transaction-context"]
resolver = "3"

[workspace.package]
Expand All @@ -20,6 +16,7 @@ magic-root-interface = { path = "programs/magic-root-interface" }
magic-root-program = { path = "programs/magic-root-program" }
solana-account = { path = "solana/account" }
solana-program-runtime = { path = "solana/program-runtime" }
solana-svm = { path = "solana/svm" }
solana-transaction-context = { path = "solana/transaction-context" }

ahash = "0.8.12"
Expand All @@ -32,6 +29,7 @@ blake3 = "1.8.5"
cfg-if = "1.0.4"
criterion = "0.7.0"
derive_more = "2.1.1"
env_logger = "0.11.8"
itertools = "0.14.0"
log = "0.4.29"
qualifier_attr = "0.2.2"
Expand All @@ -54,9 +52,11 @@ solana-account-info = "3.1.1"
solana-clock = "3.1.0"
solana-compute-budget-instruction = "4.1.1"
solana-cpi = "3.1.0"
solana-ed25519-program = "3.0.0"
solana-epoch-rewards = "3.0.1"
solana-epoch-schedule = "3.1.0"
solana-feature-gate-interface = { version = "4.0.0", features = ["bincode"] }
solana-fee-calculator = "3.2.0"
solana-fee-structure = "3.0.0"
solana-frozen-abi = "3.3.0"
solana-frozen-abi-macro = "3.3.0"
Expand All @@ -66,7 +66,12 @@ solana-instruction-error = "2.3.0"
solana-instructions-sysvar = "4.0.0"
solana-keypair = "3.1.2"
solana-last-restart-slot = "3.0.0"
solana-loader-v3-interface = "6.1.0"
solana-loader-v3-interface = "7.0.0"
solana-message = "4.1.1"
solana-msg = "3.1.0"
solana-native-token = "3.0.0"
solana-packet = "4.1.0"
solana-precompile-error = "3.0.0"
solana-program-entrypoint = "3.1.1"
solana-program-error = "3.0.1"
solana-pubkey = "4.2.0"
Expand All @@ -78,25 +83,27 @@ solana-short-vec = "3.2.1"
solana-signature = "3.4.0"
solana-signer = "3.0.1"
solana-slot-hashes = "3.0.1"
solana-stable-layout = "3.0.0"
solana-sysvar = "3.1.1"
solana-sysvar-id = "3.1.0"
solana-system-interface = ">=3.0.0, <3.2.0"
solana-svm-callback = "4.0.0-rc.1"
solana-svm-feature-set = "4.0.0-rc.1"
solana-svm-log-collector = "4.0.0-rc.1"
solana-svm-measure = "4.0.0-rc.1"
solana-svm-timings = "4.0.0-rc.1"
solana-svm-transaction = "4.0.0-rc.1"
solana-svm-type-overrides = "4.0.0-rc.1"
solana-system-interface = ">=3.0.0, <3.2.0"
solana-system-program = "4.0.0-rc.0"
solana-system-transaction = "3.0.0"
solana-sysvar = "3.1.1"
solana-stable-layout = "3.0.1"
solana-svm-callback = "4.1.1"
solana-svm-feature-set = "4.1.1"
solana-svm-log-collector = "4.1.1"
solana-svm-measure = "4.1.1"
solana-svm-timings = "4.1.1"
solana-svm-transaction = "4.1.1"
solana-svm-type-overrides = "4.1.1"
solana-system-interface = { version = "3.2", features = ["alloc", "bincode", "serde", "wincode"] }
solana-system-program = "4.1.0"
solana-sysvar = "4.0.0"
solana-sysvar-id = "3.1.0"
solana-transaction = "4.1.1"
solana-transaction-error = "3.2.0"

[patch.crates-io]
solana-account = { path = "solana/account" }
solana-program-runtime = { path = "solana/program-runtime" }
solana-svm = { path = "solana/svm" }
solana-transaction-context = { path = "solana/transaction-context" }


[workspace.lints.rust]
missing_docs = "deny"
Expand Down
177 changes: 177 additions & 0 deletions solana/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Engine Runtime Differences from Agave

This directory holds the engine's forks of the Agave runtime crates. They are
forked rather than used unmodified because the engine is an execution engine, not
a validator runtime: the code here executes already-loaded transactions and
returns the resulting account changes to the caller. It does not handle consensus,
fork choice, confirmations, validator fee policy, or bank commit ownership — those
concerns belong to the layers above.

This document is the maintainer reference for the fork. It records which
divergences from upstream are intentional and must be preserved, as opposed to
accidental drift that should be fixed. Before changing SVM execution, account
representation, or VM account mapping, read the relevant section: several of these
behaviors are load-bearing in non-obvious ways, and some must be kept in sync
across more than one crate. The per-crate READMEs point back here rather than
repeating it.

## Runtime Scope

The SVM path is caller-owned at the transaction boundary. Concretely:

- `solana-svm` loads accounts through a caller-provided callback and returns the
mutated account set in the executed transaction.
- Persistence, commit policy, deployment-slot checks, and validator-style batch
decisions all live *outside* these crates.
- Program loading is limited to the programs a transaction actually needs, and it
checks native-loader or `PROGRAM_OWNERS` ownership before execution.
- Rent-state and lamport-balance checks still run around execution — but this
runtime does not own validator state, so that's as far as it goes.

## Account Model

`solana-account` replaces the standard shared-data shape with an engine account
representation built around copy-on-write storage.

`AccountSharedData` is one of:

- `CoWAccount::Owned`, backed by an `Arc<Vec<u8>>`.
- `CoWAccount::Borrowed`, a zero-copy view into an aligned external buffer.

It also carries `DirtyMarkers`, which record field-level changes to data, owner,
lamports, slot, mode, and state flags — these are the writeback signal the caller
reads after execution.

The borrowed layout is fixed, and documented in full in
`solana/account/README.md`. The invariants that matter here:

- Borrowed buffers must be 8-byte aligned and stay live for the whole borrow.
- The buffer holds an `AccountHeader`, a shared pubkey prefix, and two account
images; `AccountHeader::sequence` selects the active one.
- `translate()` copies the active image into the shadow image before mutation.
- `commit()` publishes the shadow image by advancing the sequence counter.
- `reset()` discards shadow writes by repointing back to the active image.
- `rollback()` is only valid after a `commit()`.

Writes try to stay borrowed as long as they fit the borrowed image capacity. A
write that doesn't fit promotes the account to owned heap storage, and a shared
owned buffer goes through `Arc::make_mut` before being mutated.

The account core also carries engine state that Agave's account code doesn't model
the same way:

- `AccountMode` distinguishes `ReadOnly`, `Placeholder`, `Authority`, `System`,
`Delegated`, `Ephemeral`, `Transient`, and `Closed`.
- `AccountSharedData::mutable()` is true only for `Delegated`, `Ephemeral`, and
`Transient` — this is the predicate higher layers route persisted-vs-volatile on.
- `StateFlags` include `EXECUTABLE` and `COMPRESSED`.
- `rent_epoch` is *not* stored in `AccountSharedData`; compatibility constructors
ignore it, and runtime serialization masks rent-epoch values for program inputs.

## Transaction Context

`solana-transaction-context` keeps transaction accounts in `UnsafeCell`
containers guarded by explicit borrow counters. This keeps account borrows local
to the transaction context while still letting VM access handlers remap account
data when a program writes through a mapped region — something ordinary borrows
can't express.

Maintainer-visible differences:

- `TransactionAccounts` tracks touched accounts, the total account-data resize
delta, and the per-instruction lamports delta.
- `AccountRef` and `AccountRefMut` release their custom borrow counters on drop.
- `TransactionAccountViewMut::reserve()` can reserve CoW capacity ahead of a VM
write path.
- `ExecutionRecord` returns the keyed accounts, return data, touched-account
count, and account resize delta after execution.
- `TransactionContext::push()` updates the instructions-sysvar current index when
that sysvar is present.

All outstanding references must be gone before a transaction context is
deconstructed. If `Rc::try_unwrap` fails during deconstruction, that's a real
lifetime bug, not a tolerable edge case — treat it as one.

## VM Account Mapping

Account data is always directly mapped into the SBF VM. The old local-runtime
threading for `virtual_address_space_adjustments` and
`account_data_direct_mapping` is intentionally absent. **Do not** reintroduce
branches that copy account data through the serialized input buffer.

Serialization still preserves the loader-selected ABI formats:

- Deprecated-loader accounts use ABI-v0 metadata.
- Loader-v2 and loader-v3 accounts use ABI-v1 metadata.
- ABI-v1 may append the direct account-pointer array when
`direct_account_pointers_in_program_input` is enabled.

The serialized input buffer carries account metadata, lamports, lengths, owner,
instruction data, and program id — but **not** account data bytes. The data lives
in separate `MemoryRegion`s:

- Loader-v1 account regions reserve exactly the current data length.
- Loader-v2 and loader-v3 account regions reserve the current length plus
`MAX_PERMITTED_DATA_INCREASE`.
- Non-loader-v1 serialization still adds alignment padding so VM and host
addresses stay aligned across account-region gaps.

Deserialization reads lamports, owners, and lengths back from the serialized
metadata. It does *not* copy account bytes out of the serialized buffer, because
data mutations already happened through the mapped account regions. ABI-v1 still
enforces realloc limits and the maximum account-data length.

## Access-Violation Growth

Writable account data may be mapped read-only at first. That's deliberate for
borrowed accounts and shared owned buffers: the first VM store has to pass through
the transaction-context access-violation handler, so the account can be touched,
translated or made unique, resized if needed, and remapped.

The handler:

- Only handles stores. Loads beyond mapped data stay load violations.
- Requires a `MemoryRegion` payload carrying the transaction account index.
- Ignores accesses beyond the address space reserved for that account.
- Updates touched flags and the account resize delta before resizing.
- Grows only to the requested access length — not eagerly to the full permitted
growth range.
- Replaces the region's host pointer, length, and writability after obtaining the
current account-data slice.

VM execution maps account-region access violations back into account-specific
errors — readonly data modification, account data too small, invalid realloc.
Keep `serialization`, `transaction-context::access_violation_handler`, and
`program-runtime::vm` error remapping in sync; they're three views of the same
contract.

## CPI Account Synchronization

CPI helpers follow the same direct-mapped account-data rule:

- `CallerAccount::serialized_data` is always empty for account data.
- CPI entry syncs caller lamports, owner, and data length into the callee account.
It does not copy data bytes out of caller serialized buffers.
- CPI exit syncs lamports, owner, and length back into the caller's `AccountInfo`
fields.
- If a length or owner change may have moved account storage, CPI replaces the
caller account-data `MemoryRegion` with a fresh region from
`create_memory_region_of_account`.
- Inner-instruction realloc limits are based on the caller's original data length
plus the permitted growth range — except deprecated-loader callers under strict
address checks, which reserve only the original length.

Any change to account-region layout has to update CPI region replacement and the
VM access-violation path *together*; they can't drift apart.

## Maintainer Rules

- Preserve direct account-region mapping as the single runtime behavior.
- Preserve ABI-v0 and ABI-v1 metadata compatibility — but do not restore the old
"copy account data into the serialized input buffer" mode.
- Keep borrowed-account layout changes synchronized across `solana-account`,
`solana-transaction-context`, and the runtime mapping tests.
- Treat dirty markers and touched flags as the writeback signal for the caller.
- Don't add validator flow to these crates. If a behavior needs persistence, batch
commit, consensus, or validator fee decisions, it belongs *outside* this runtime
layer.
4 changes: 1 addition & 3 deletions solana/program-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ name = "solana-program-runtime"
authors = { workspace = true }
description = "Solana program runtime"
documentation = "https://docs.rs/solana-program-runtime"
version = { workspace = true }
authors = { workspace = true }
repository = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
Expand Down
95 changes: 95 additions & 0 deletions solana/svm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
[package]
name = "solana-svm"

authors = { workspace = true }
description = "Solana SVM"
documentation = "https://docs.rs/solana-svm"
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
version = "4.1.1"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[lib]
crate-type = ["lib"]
name = "solana_svm"

[features]
# No-op stub retained only so external (patched-in) crates that reference
# `solana-svm/agave-unstable-api` still resolve; the lib is no longer gated on
# it, and the upstream svm-* deps enable their unstable API unconditionally below.
agave-unstable-api = []
dev-context-only-utils = ["dep:qualifier_attr", "solana-program-runtime/dev-context-only-utils"]
frozen-abi = [
"dep:solana-frozen-abi",
"dep:solana-frozen-abi-macro",
"solana-program-runtime/frozen-abi"
]
shuttle-test = ["solana-program-runtime/shuttle-test", "solana-svm-type-overrides/shuttle-test"]
svm-internal = ["dep:qualifier_attr"]

[dependencies]
magic-root-interface = { workspace = true }

ahash = { workspace = true }
log = { workspace = true }
qualifier_attr = { workspace = true, optional = true }
scc = { workspace = true }
serde = { workspace = true, features = ["rc"] }
thiserror = { workspace = true }

solana-account = { workspace = true }
solana-clock = { workspace = true }
solana-fee-structure = { workspace = true }
solana-frozen-abi = { workspace = true, optional = true, features = ["frozen-abi"] }
solana-frozen-abi-macro = { workspace = true, optional = true, features = ["frozen-abi"] }
solana-hash = { workspace = true }
solana-instruction = { workspace = true, features = ["std"] }
solana-instructions-sysvar = { workspace = true }
solana-loader-v3-interface = { workspace = true, features = ["bincode"] }
solana-message = { workspace = true }
solana-program-entrypoint = { workspace = true }
solana-program-runtime = { workspace = true }
solana-pubkey = { workspace = true }
solana-rent = { workspace = true }
solana-sdk-ids = { workspace = true }
solana-svm-callback = { workspace = true, features = ["agave-unstable-api"] }
solana-svm-feature-set = { workspace = true, features = ["agave-unstable-api"] }
solana-svm-log-collector = { workspace = true, features = ["agave-unstable-api"] }
solana-svm-measure = { workspace = true, features = ["agave-unstable-api"] }
solana-svm-timings = { workspace = true, features = ["agave-unstable-api"] }
solana-svm-transaction = { workspace = true, features = ["agave-unstable-api"] }
solana-svm-type-overrides = { workspace = true, features = ["agave-unstable-api"] }
solana-system-interface = { workspace = true }
solana-sysvar-id = { workspace = true }
solana-transaction-context = { workspace = true }
solana-transaction-error = { workspace = true }

[dev-dependencies]
bincode = { workspace = true }
env_logger = { workspace = true }
rand = { workspace = true }
solana-clock = { workspace = true }
solana-ed25519-program = { workspace = true }
solana-epoch-schedule = { workspace = true }
solana-fee-calculator = { workspace = true }
solana-keypair = { workspace = true }
solana-native-token = { workspace = true }
solana-precompile-error = { workspace = true }
solana-program-runtime = { workspace = true, features = ["dev-context-only-utils"] }
solana-pubkey = { workspace = true, features = ["rand"] }
solana-rent = { workspace = true }
solana-sbpf = { workspace = true, features = ["jit"] }
solana-signature = { workspace = true, features = ["rand"] }
solana-signer = { workspace = true }
# See order-crates-for-publishing.py for using this unusual `path = "."`
solana-svm = { path = ".", features = ["dev-context-only-utils", "svm-internal"] }
solana-sysvar = { workspace = true }
solana-transaction = { workspace = true, features = ["dev-context-only-utils"] }
solana-transaction-context = { workspace = true, features = ["dev-context-only-utils"] }

[lints.rust]
unexpected_cfgs = "allow"
18 changes: 18 additions & 0 deletions solana/svm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# `solana-svm`

A fork of Agave's `solana-svm` (`anza-xyz/agave`), reshaped for the engine and
patched in workspace-wide through `[patch.crates-io]`.

This is the transaction-level entry point into execution. It loads the accounts a
transaction needs — through a caller-provided callback,
`transaction_processing_callback`, rather than owning any storage itself —
executes through `solana-program-runtime`, and hands back the mutated account set.
The callback is the seam: it's how the engine plugs `keeper`/`accountsdb` in as
the account source without this crate knowing anything about them.

Everything a validator would do *around* execution — committing results, batch
decisions, deployment-slot checks — stays above this crate. It loads, runs, and
returns; nothing more.

The engine's intentional divergences from upstream are documented for the whole
fork in [`../README.md`](../README.md).
Loading