Skip to content

Commit 607cd80

Browse files
committed
feat: add customized solana-account crate
1 parent 5a3b70e commit 607cd80

19 files changed

Lines changed: 2332 additions & 1 deletion

‎Cargo.toml‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[workspace]
2+
members = ["solana/account"]
23
resolver = "3"
34

45
[workspace.package]
@@ -10,6 +11,35 @@ repository = "https://github.com/magicblock-labs/engine"
1011
rust-version = "1.94.1"
1112
version = "0.1.0"
1213

14+
[workspace.dependencies]
15+
solana-account = { path = "solana/account" }
16+
17+
ahash = "0.8.12"
18+
arc-swap = "1.9.1"
19+
bincode = "1.3.3"
20+
bitflags = "2.11.1"
21+
qualifier_attr = "0.2.2"
22+
rand = "0.8.5"
23+
rustix = { version = "1.1.4" }
24+
serde = "1.0.228"
25+
serde_bytes = "0.11.19"
26+
tar = "0.4.45"
27+
thiserror = "2.0.17"
28+
wincode = "0.5.1"
29+
zstd = { version = "0.13.3", default-features = false }
30+
31+
agave-feature-set = { version = "3.1.14", features = ["agave-unstable-api"] }
32+
agave-transaction-view = { version = "3.1.13", features = ["agave-unstable-api"] }
33+
solana-account-info = "3.1.1"
34+
solana-clock = "3.0.1"
35+
solana-hash = "4.3.0"
36+
solana-instruction-error = "2.3.0"
37+
solana-pubkey = "4.2.0"
38+
solana-sdk-ids = "3.1.0"
39+
solana-sysvar = "4.0.0"
40+
solana-transaction-error = "3.2"
41+
42+
1343
[workspace.lints.rust]
1444
missing_docs = "deny"
1545
rust_2018_idioms = { level = "warn", priority = -1 }

‎solana/account/Cargo.toml‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[package]
2+
name = "solana-account"
3+
4+
authors = { workspace = true }
5+
description = "Solana Account type"
6+
edition = { workspace = true }
7+
homepage = { workspace = true }
8+
license = { workspace = true }
9+
readme = "README.md"
10+
repository = { workspace = true }
11+
version = "3.4.0"
12+
13+
[features]
14+
bincode = ["dep:bincode", "dep:solana-sysvar", "serde"]
15+
dev-context-only-utils = ["bincode", "dep:qualifier_attr"]
16+
serde = ["dep:serde", "dep:serde_bytes", "serde/rc", "solana-pubkey/serde"]
17+
wincode = ["dep:wincode", "solana-pubkey/wincode"]
18+
19+
[dependencies]
20+
bincode = { workspace = true, optional = true }
21+
bitflags = { workspace = true, features = ["serde"] }
22+
qualifier_attr = { workspace = true, optional = true }
23+
serde = { workspace = true, optional = true }
24+
serde_bytes = { workspace = true, optional = true }
25+
solana-account-info = { workspace = true }
26+
solana-clock = { workspace = true }
27+
solana-instruction-error = { workspace = true }
28+
solana-pubkey = { workspace = true }
29+
solana-sdk-ids = { workspace = true }
30+
solana-sysvar = { workspace = true, features = ["bincode"], optional = true }
31+
wincode = { workspace = true, features = ["alloc"], optional = true }
32+
33+
[dev-dependencies]
34+
solana-account = { path = ".", features = ["dev-context-only-utils"] }
35+
solana-pubkey = { workspace = true, features = ["std"] }
36+
37+
[lints]
38+
workspace = true

‎solana/account/README.md‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# `solana-account`
2+
3+
Account types for the engine account store.
4+
5+
This is a small fork of Solana account code shaped around the storage model used
6+
by `accountsdb`. It owns the account representation and serialization details;
7+
routing decisions stay in the storage layer.
8+
9+
There are two account shapes:
10+
11+
- `Account` is fully owned. Its data lives in a `Vec<u8>`
12+
- `AccountSharedData` is copy-on-write. It can point at an aligned external
13+
buffer and only promotes to owned storage when a write can no longer fit in
14+
the borrowed allocation.
15+
16+
Borrowed storage is layout-bound, not source-bound. The backing memory may come
17+
from mmap, a file-backed arena, or a test buffer. It must be 8-byte aligned,
18+
match the layout in `cow::borrowed`, and stay live with unique mutable access
19+
for the whole borrow.
20+
21+
## Borrowed layout
22+
23+
| Part | Offset | Contents |
24+
| ------- | ------------- | ------------------------- |
25+
| header | start | sequence and image size |
26+
| pubkey | after header | account pubkey |
27+
| image A | after pubkey | core state and data bytes |
28+
| image B | after image A | core state and data bytes |
29+
30+
The header selects the active image. The pubkey is shared by both images. The
31+
inactive image is the shadow copy used when rewriting borrowed state.
32+
33+
`OwnedAccount::units` reports the serialized storage size in storage units.
34+
`BorrowedAccount::span` reports the full borrowed span.
35+
36+
Higher layers use `AccountSharedData::mutable()` to decide whether an account
37+
belongs in persisted or volatile storage.

‎solana/account/src/account.rs‎

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use {
2+
crate::{AccountSharedData, ReadableAccount, traits::debug_fmt},
3+
solana_account_info::AccountInfo,
4+
solana_clock::Epoch,
5+
solana_pubkey::Pubkey,
6+
solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable},
7+
std::{cell::RefCell, fmt, rc::Rc},
8+
};
9+
10+
/// An on-chain account with owned data and an explicit rent epoch.
11+
#[repr(C)]
12+
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(rename_all = "camelCase"))]
13+
#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
14+
#[derive(PartialEq, Eq, Clone, Default)]
15+
pub struct Account {
16+
/// Lamports in the account.
17+
pub lamports: u64,
18+
/// Data held in the account.
19+
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
20+
pub data: Vec<u8>,
21+
/// The program that owns this account.
22+
pub owner: Pubkey,
23+
/// Whether the account contains executable program data.
24+
pub executable: bool,
25+
/// The epoch at which this account next owes rent.
26+
pub rent_epoch: Epoch,
27+
}
28+
29+
#[cfg(feature = "serde")]
30+
mod account_serialize {
31+
use {
32+
crate::ReadableAccount,
33+
serde::{Serialize, ser::Serializer},
34+
solana_clock::Epoch,
35+
solana_pubkey::Pubkey,
36+
};
37+
38+
#[repr(C)]
39+
#[derive(serde::Serialize)]
40+
#[serde(rename_all = "camelCase")]
41+
/// Serialization shape shared by `Account` and `AccountSharedData`.
42+
struct Account<'a> {
43+
lamports: u64,
44+
#[serde(with = "serde_bytes")]
45+
data: &'a [u8],
46+
owner: &'a Pubkey,
47+
executable: bool,
48+
rent_epoch: Epoch,
49+
}
50+
51+
/// Serializes any readable account using the canonical `Account` layout.
52+
pub(crate) fn serialize_account<S>(
53+
account: &impl ReadableAccount,
54+
serializer: S,
55+
) -> Result<S::Ok, S::Error>
56+
where
57+
S: Serializer,
58+
{
59+
let account = Account {
60+
lamports: account.lamports(),
61+
data: account.data(),
62+
owner: account.owner(),
63+
executable: account.executable(),
64+
rent_epoch: account.rent_epoch(),
65+
};
66+
account.serialize(serializer)
67+
}
68+
}
69+
70+
#[cfg(feature = "serde")]
71+
impl serde::ser::Serialize for Account {
72+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73+
where
74+
S: serde::ser::Serializer,
75+
{
76+
account_serialize::serialize_account(self, serializer)
77+
}
78+
}
79+
80+
#[cfg(feature = "serde")]
81+
impl serde::ser::Serialize for AccountSharedData {
82+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83+
where
84+
S: serde::ser::Serializer,
85+
{
86+
account_serialize::serialize_account(self, serializer)
87+
}
88+
}
89+
90+
impl From<AccountSharedData> for Account {
91+
fn from(other: AccountSharedData) -> Self {
92+
Self {
93+
lamports: other.lamports(),
94+
data: other.data().to_vec(),
95+
owner: *other.owner(),
96+
executable: other.executable(),
97+
rent_epoch: other.rent_epoch(),
98+
}
99+
}
100+
}
101+
102+
impl fmt::Debug for Account {
103+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104+
debug_fmt(self, f)
105+
}
106+
}
107+
108+
impl fmt::Debug for AccountSharedData {
109+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110+
debug_fmt(self, f)
111+
}
112+
}
113+
114+
impl Account {
115+
/// Builds an account from its exact field set.
116+
///
117+
/// Used by the constructors to keep the owned layout in one place.
118+
fn from_parts(
119+
lamports: u64,
120+
data: Vec<u8>,
121+
owner: Pubkey,
122+
executable: bool,
123+
rent_epoch: Epoch,
124+
) -> Self {
125+
Self {
126+
lamports,
127+
data,
128+
owner,
129+
executable,
130+
rent_epoch,
131+
}
132+
}
133+
134+
/// Creates a new account with zero-filled data.
135+
pub fn new(lamports: u64, space: usize, owner: &Pubkey) -> Self {
136+
Self::new_rent_epoch(lamports, space, owner, Epoch::default())
137+
}
138+
139+
/// Creates a new account wrapped in a `RefCell`.
140+
pub fn new_ref(lamports: u64, space: usize, owner: &Pubkey) -> Rc<RefCell<Self>> {
141+
Rc::new(RefCell::new(Self::new(lamports, space, owner)))
142+
}
143+
144+
/// Creates a new account whose data is the serialized state.
145+
#[cfg(feature = "bincode")]
146+
pub fn new_data<T: serde::Serialize>(
147+
lamports: u64,
148+
state: &T,
149+
owner: &Pubkey,
150+
) -> Result<Self, bincode::Error> {
151+
let data = bincode::serialize(state)?;
152+
Ok(Self::from_parts(
153+
lamports,
154+
data,
155+
*owner,
156+
false,
157+
Epoch::default(),
158+
))
159+
}
160+
161+
/// Creates a new serialized account wrapped in a `RefCell`.
162+
#[cfg(feature = "bincode")]
163+
pub fn new_ref_data<T: serde::Serialize>(
164+
lamports: u64,
165+
state: &T,
166+
owner: &Pubkey,
167+
) -> Result<RefCell<Self>, bincode::Error> {
168+
Self::new_data(lamports, state, owner).map(RefCell::new)
169+
}
170+
171+
/// Creates a new account with fixed space and serialized state.
172+
#[cfg(feature = "bincode")]
173+
pub fn new_data_with_space<T: serde::Serialize>(
174+
lamports: u64,
175+
state: &T,
176+
space: usize,
177+
owner: &Pubkey,
178+
) -> Result<Self, bincode::Error> {
179+
let mut account = Self::new(lamports, space, owner);
180+
crate::codec::serialize_data(&mut account, state)?;
181+
Ok(account)
182+
}
183+
184+
/// Creates a new fixed-size serialized account wrapped in a `RefCell`.
185+
#[cfg(feature = "bincode")]
186+
pub fn new_ref_data_with_space<T: serde::Serialize>(
187+
lamports: u64,
188+
state: &T,
189+
space: usize,
190+
owner: &Pubkey,
191+
) -> Result<RefCell<Self>, bincode::Error> {
192+
Self::new_data_with_space(lamports, state, space, owner).map(RefCell::new)
193+
}
194+
195+
/// Creates a new account with an explicit rent epoch.
196+
pub fn new_rent_epoch(lamports: u64, space: usize, owner: &Pubkey, rent_epoch: Epoch) -> Self {
197+
Self::from_parts(lamports, vec![0; space], *owner, false, rent_epoch)
198+
}
199+
200+
/// Deserializes the account data as `T`.
201+
#[cfg(feature = "bincode")]
202+
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
203+
crate::codec::deserialize_data(self)
204+
}
205+
206+
/// Serializes `state` into the existing account data buffer.
207+
#[cfg(feature = "bincode")]
208+
pub fn serialize_data<T: serde::Serialize>(&mut self, state: &T) -> Result<(), bincode::Error> {
209+
crate::codec::serialize_data(self, state)
210+
}
211+
}
212+
213+
impl solana_account_info::Account for Account {
214+
fn get(&mut self) -> (&mut u64, &mut [u8], &Pubkey, bool) {
215+
(
216+
&mut self.lamports,
217+
&mut self.data,
218+
&self.owner,
219+
self.executable,
220+
)
221+
}
222+
}
223+
224+
/// Builds `AccountInfo` values for accounts and signer bits.
225+
///
226+
/// The returned infos borrow the provided accounts directly.
227+
pub fn create_is_signer_account_infos<'a>(
228+
accounts: &'a mut [(&'a Pubkey, bool, &'a mut Account)],
229+
) -> Vec<AccountInfo<'a>> {
230+
accounts
231+
.iter_mut()
232+
.map(|(key, is_signer, account)| {
233+
AccountInfo::new(
234+
key,
235+
*is_signer,
236+
false,
237+
&mut account.lamports,
238+
&mut account.data,
239+
&account.owner,
240+
account.executable,
241+
)
242+
})
243+
.collect()
244+
}
245+
246+
/// Owners that imply the account contains a loaded program.
247+
pub const PROGRAM_OWNERS: &[Pubkey] =
248+
&[bpf_loader_upgradeable::id(), bpf_loader::id(), bpf_loader_deprecated::id()];

‎solana/account/src/codec.rs‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! Shared bincode helpers for account data.
2+
3+
use serde::{Serialize, de::DeserializeOwned};
4+
5+
use crate::{ReadableAccount, WritableAccount};
6+
7+
/// Deserializes typed state from an account data slice.
8+
pub(crate) fn deserialize_data<T: DeserializeOwned, U: ReadableAccount>(
9+
account: &U,
10+
) -> Result<T, bincode::Error> {
11+
bincode::deserialize(account.data())
12+
}
13+
14+
/// Serializes typed state into an existing account data buffer.
15+
pub(crate) fn serialize_data<T: Serialize, U: WritableAccount>(
16+
account: &mut U,
17+
state: &T,
18+
) -> Result<(), bincode::Error> {
19+
if bincode::serialized_size(state)? > account.data().len() as u64 {
20+
return Err(Box::new(bincode::ErrorKind::SizeLimit));
21+
}
22+
bincode::serialize_into(account.data_as_mut_slice(), state)
23+
}

0 commit comments

Comments
 (0)