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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.rs linguist-language=Rust
Cargo.toml linguist-language=Rust
Cargo.lock linguist-language=Rust
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
Cargo.lock
AGENTS.md
CLAUDE.md
38 changes: 38 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[workspace]
members = ["solana/account"]
resolver = "3"

[workspace.package]
Expand All @@ -10,6 +11,43 @@ repository = "https://github.com/magicblock-labs/engine"
rust-version = "1.94.1"
version = "0.1.0"

[workspace.dependencies]
magic-root-interface = { path = "programs/magic-root-interface" }
magic-root-program = { path = "programs/magic-root-program" }
solana-account = { path = "solana/account" }

ahash = "0.8.12"
arc-swap = "1.9.1"
bincode = "1.3.3"
bitflags = "2.11.1"
blake3 = "1.8.5"
qualifier_attr = "0.2.2"
rand = "0.8.5"
rustix = { version = "1.1.4" }
serde = "1.0.228"
serde_bytes = "0.11.19"
tar = "0.4.45"
tempfile = "3"
thiserror = "2.0.17"
tracing-subscriber = { version = "0.3.23", features = ["env-filter", "fmt"] }
wincode = "0.5.1"
zstd = { version = "0.13.3", default-features = false }

agave-feature-set = { version = "3.1.14", features = ["agave-unstable-api"] }
agave-transaction-view = { version = "3.1.13", features = ["agave-unstable-api"] }
solana-account-info = "3.1.1"
solana-clock = "3.1.0"
solana-compute-budget-instruction = "4.1.1"
solana-cpi = "3.1.0"
solana-hash = "4.3.0"
solana-instruction-error = "2.3.0"
solana-program-error = "3.0.1"
solana-pubkey = "4.2.0"
solana-sdk-ids = "3.1.0"
solana-sysvar = "4.0.0"
solana-transaction-error = "3.2.0"


[workspace.lints.rust]
missing_docs = "deny"
rust_2018_idioms = { level = "warn", priority = -1 }
Expand Down
38 changes: 38 additions & 0 deletions solana/account/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[package]
name = "solana-account"

authors = { workspace = true }
description = "Solana Account type"
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
readme = "README.md"
repository = { workspace = true }
version = "4.3.1"

[features]
bincode = ["dep:bincode", "dep:solana-sysvar", "serde"]
dev-context-only-utils = ["bincode", "dep:qualifier_attr"]
serde = ["dep:serde", "dep:serde_bytes", "serde/rc", "solana-pubkey/serde"]
wincode = ["bincode", "dep:wincode", "solana-pubkey/wincode"]

[dependencies]
bincode = { workspace = true, optional = true }
bitflags = { workspace = true, features = ["serde"] }
qualifier_attr = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_bytes = { workspace = true, optional = true }
solana-account-info = { workspace = true }
solana-clock = { workspace = true }
solana-instruction-error = { workspace = true }
solana-pubkey = { workspace = true }
solana-sdk-ids = { workspace = true }
solana-sysvar = { workspace = true, features = ["bincode"], optional = true }
wincode = { workspace = true, features = ["alloc"], optional = true }

[dev-dependencies]
solana-account = { path = ".", features = ["dev-context-only-utils"] }
solana-pubkey = { workspace = true, features = ["std"] }

[lints]
workspace = true
36 changes: 36 additions & 0 deletions solana/account/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `solana-account`

A fork of Solana's account code, reshaped around the engine's storage model. It
owns how an account is represented in memory and how it's serialized; *where* an
account lives — persisted or volatile — is decided one layer up, in the storage
crates.

The reason for the fork is copy-on-write. `Account` is the simple, fully-owned
form (`Vec<u8>`). `AccountSharedData` is the one the engine actually runs on: it
can point straight at an aligned external buffer and only promotes to owned heap
storage when a write no longer fits the borrowed allocation. That lets the engine
read and even mutate accounts directly out of the mmap'd persisted store without
copying them first.

Borrowed storage is **layout-bound, not source-bound**: it doesn't care whether
the bytes come from an mmap, a file arena, or a test buffer, as long as the buffer
is 8-byte aligned, matches the `cow::borrowed` layout, and stays alive with unique
mutable access for the entire borrow. Violate any of those and the borrow is
unsound, so they're hard requirements, not guidelines.

## Borrowed layout

| Part | Offset | Contents |
| ------- | ------------- | ------------------------- |
| header | start | sequence and image size |
| pubkey | after header | account pubkey |
| image A | after pubkey | core state and data bytes |
| image B | after image A | core state and data bytes |

There are two images because writes are double-buffered. The header's sequence
counter selects which image is *active*; the other is the shadow copy that
rewrites land in before being published. The pubkey sits once, shared between
them.

Higher layers route on `AccountSharedData::mutable()` to decide persisted vs.
volatile.
255 changes: 255 additions & 0 deletions solana/account/src/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
use {
crate::{AccountSharedData, ReadableAccount, traits::debug_fmt},
solana_account_info::AccountInfo,
solana_clock::Epoch,
solana_pubkey::Pubkey,
solana_sdk_ids::{
bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
},
std::{cell::RefCell, fmt, rc::Rc},
};

/// An on-chain account with owned data and an explicit rent epoch.
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
#[derive(PartialEq, Eq, Clone, Default)]
pub struct Account {
/// Lamports in the account.
pub lamports: u64,
/// Data held in the account.
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
pub data: Vec<u8>,
/// The program that owns this account.
pub owner: Pubkey,
/// Whether the account contains executable program data.
pub executable: bool,
/// The epoch at which this account next owes rent.
pub rent_epoch: Epoch,
}

#[cfg(feature = "serde")]
mod account_serialize {
use {
crate::ReadableAccount,
serde::{Serialize, ser::Serializer},
solana_clock::Epoch,
solana_pubkey::Pubkey,
};

#[repr(C)]
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
/// Serialization shape shared by `Account` and `AccountSharedData`.
struct Account<'a> {
lamports: u64,
#[serde(with = "serde_bytes")]
data: &'a [u8],
owner: &'a Pubkey,
executable: bool,
rent_epoch: Epoch,
}

/// Serializes any readable account using the canonical `Account` layout.
pub(crate) fn serialize_account<S>(
account: &impl ReadableAccount,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let account = Account {
lamports: account.lamports(),
data: account.data(),
owner: account.owner(),
executable: account.executable(),
rent_epoch: account.rent_epoch(),
};
account.serialize(serializer)
}
}

#[cfg(feature = "serde")]
impl serde::ser::Serialize for Account {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
account_serialize::serialize_account(self, serializer)
}
}

#[cfg(feature = "serde")]
impl serde::ser::Serialize for AccountSharedData {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
account_serialize::serialize_account(self, serializer)
}
}

impl From<AccountSharedData> for Account {
fn from(other: AccountSharedData) -> Self {
Self {
lamports: other.lamports(),
data: other.data().to_vec(),
owner: *other.owner(),
executable: other.executable(),
rent_epoch: other.rent_epoch(),
}
}
}

impl fmt::Debug for Account {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
debug_fmt(self, f)
}
}

impl fmt::Debug for AccountSharedData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
debug_fmt(self, f)
}
}

impl Account {
/// Builds an account from its exact field set.
///
/// Used by the constructors to keep the owned layout in one place.
fn from_parts(
lamports: u64,
data: Vec<u8>,
owner: Pubkey,
executable: bool,
rent_epoch: Epoch,
) -> Self {
Self {
lamports,
data,
owner,
executable,
rent_epoch,
}
}

/// Creates a new account with zero-filled data.
pub fn new(lamports: u64, space: usize, owner: &Pubkey) -> Self {
Self::new_rent_epoch(lamports, space, owner, Epoch::default())
}

/// Creates a new account wrapped in a `RefCell`.
pub fn new_ref(lamports: u64, space: usize, owner: &Pubkey) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self::new(lamports, space, owner)))
}

/// Creates a new account whose data is the serialized state.
#[cfg(feature = "bincode")]
pub fn new_data<T: serde::Serialize>(
lamports: u64,
state: &T,
owner: &Pubkey,
) -> Result<Self, bincode::Error> {
let data = bincode::serialize(state)?;
Ok(Self::from_parts(
lamports,
data,
*owner,
false,
Epoch::default(),
))
}

/// Creates a new serialized account wrapped in a `RefCell`.
#[cfg(feature = "bincode")]
pub fn new_ref_data<T: serde::Serialize>(
lamports: u64,
state: &T,
owner: &Pubkey,
) -> Result<RefCell<Self>, bincode::Error> {
Self::new_data(lamports, state, owner).map(RefCell::new)
}

/// Creates a new account with fixed space and serialized state.
#[cfg(feature = "bincode")]
pub fn new_data_with_space<T: serde::Serialize>(
lamports: u64,
state: &T,
space: usize,
owner: &Pubkey,
) -> Result<Self, bincode::Error> {
let mut account = Self::new(lamports, space, owner);
crate::codec::serialize_data(&mut account, state)?;
Ok(account)
}

/// Creates a new fixed-size serialized account wrapped in a `RefCell`.
#[cfg(feature = "bincode")]
pub fn new_ref_data_with_space<T: serde::Serialize>(
lamports: u64,
state: &T,
space: usize,
owner: &Pubkey,
) -> Result<RefCell<Self>, bincode::Error> {
Self::new_data_with_space(lamports, state, space, owner).map(RefCell::new)
}

/// Creates a new account with an explicit rent epoch.
pub fn new_rent_epoch(lamports: u64, space: usize, owner: &Pubkey, rent_epoch: Epoch) -> Self {
Self::from_parts(lamports, vec![0; space], *owner, false, rent_epoch)
}

/// Deserializes the account data as `T`.
#[cfg(feature = "bincode")]
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
crate::codec::deserialize_data(self)
}

/// Serializes `state` into the existing account data buffer.
#[cfg(feature = "bincode")]
pub fn serialize_data<T: serde::Serialize>(&mut self, state: &T) -> Result<(), bincode::Error> {
crate::codec::serialize_data(self, state)
}
}

impl solana_account_info::Account for Account {
fn get(&mut self) -> (&mut u64, &mut [u8], &Pubkey, bool) {
(
&mut self.lamports,
&mut self.data,
&self.owner,
self.executable,
)
}
}

/// Builds `AccountInfo` values for accounts and signer bits.
///
/// The returned infos borrow the provided accounts directly.
pub fn create_is_signer_account_infos<'a>(
accounts: &'a mut [(&'a Pubkey, bool, &'a mut Account)],
) -> Vec<AccountInfo<'a>> {
accounts
.iter_mut()
.map(|(key, is_signer, account)| {
AccountInfo::new(
key,
*is_signer,
false,
&mut account.lamports,
&mut account.data,
&account.owner,
account.executable,
)
})
.collect()
}

/// Owners that imply the account contains a loaded program.
pub const PROGRAM_OWNERS: &[Pubkey] = &[
native_loader::id(),
bpf_loader_upgradeable::id(),
bpf_loader::id(),
bpf_loader_deprecated::id(),
loader_v4::id(),
];
Loading