224 lines
8.1 KiB
Rust
224 lines
8.1 KiB
Rust
//! AccountInfo represents a reference to AccountSharedData in either an AppendVec or the write cache.
|
|
//! AccountInfo is not persisted anywhere between program runs.
|
|
//! AccountInfo is purely runtime state.
|
|
//! Note that AccountInfo is saved to disk buckets during runtime, but disk buckets are recreated at startup.
|
|
use {
|
|
crate::{
|
|
accounts_db::AppendVecId,
|
|
accounts_file::ALIGN_BOUNDARY_OFFSET,
|
|
accounts_index::{IsCached, ZeroLamport},
|
|
},
|
|
modular_bitfield::prelude::*,
|
|
};
|
|
|
|
/// offset within an append vec to account data
|
|
pub type Offset = usize;
|
|
|
|
/// bytes used to store this account in append vec
|
|
/// Note this max needs to be big enough to handle max data len of 10MB, which is a const
|
|
pub type StoredSize = u32;
|
|
|
|
/// specify where account data is located
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum StorageLocation {
|
|
AppendVec(AppendVecId, Offset),
|
|
Cached,
|
|
}
|
|
|
|
impl StorageLocation {
|
|
pub fn is_offset_equal(&self, other: &StorageLocation) -> bool {
|
|
match self {
|
|
StorageLocation::Cached => {
|
|
matches!(other, StorageLocation::Cached) // technically, 2 cached entries match in offset
|
|
}
|
|
StorageLocation::AppendVec(_, offset) => {
|
|
match other {
|
|
StorageLocation::Cached => {
|
|
false // 1 cached, 1 not
|
|
}
|
|
StorageLocation::AppendVec(_, other_offset) => other_offset == offset,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pub fn is_store_id_equal(&self, other: &StorageLocation) -> bool {
|
|
match self {
|
|
StorageLocation::Cached => {
|
|
matches!(other, StorageLocation::Cached) // 2 cached entries are same store id
|
|
}
|
|
StorageLocation::AppendVec(store_id, _) => {
|
|
match other {
|
|
StorageLocation::Cached => {
|
|
false // 1 cached, 1 not
|
|
}
|
|
StorageLocation::AppendVec(other_store_id, _) => other_store_id == store_id,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// how large the offset we store in AccountInfo is
|
|
/// Note this is a smaller datatype than 'Offset'
|
|
/// AppendVecs store accounts aligned to u64, so offset is always a multiple of 8 (sizeof(u64))
|
|
pub type OffsetReduced = u32;
|
|
|
|
/// This is an illegal value for 'offset'.
|
|
/// Account size on disk would have to be pointing to the very last 8 byte value in the max sized append vec.
|
|
/// That would mean there was a maximum size of 8 bytes for the last entry in the append vec.
|
|
/// A pubkey alone is 32 bytes, so there is no way for a valid offset to be this high of a value.
|
|
/// Realistically, a max offset is (1<<31 - 156) bytes or so for an account with zero data length. Of course, this
|
|
/// depends on the layout on disk, compression, etc. But, 8 bytes per account will never be possible.
|
|
/// So, we use this last value as a sentinel to say that the account info refers to an entry in the write cache.
|
|
const CACHED_OFFSET: OffsetReduced = (1 << (OffsetReduced::BITS - 1)) - 1;
|
|
|
|
#[bitfield(bits = 32)]
|
|
#[repr(C)]
|
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
|
pub struct PackedOffsetAndFlags {
|
|
/// this provides 2^31 bits, which when multipled by 8 (sizeof(u64)) = 16G, which is the maximum size of an append vec
|
|
offset_reduced: B31,
|
|
/// use 1 bit to specify that the entry is zero lamport
|
|
is_zero_lamport: bool,
|
|
}
|
|
|
|
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub struct AccountInfo {
|
|
/// index identifying the append storage
|
|
store_id: AppendVecId,
|
|
|
|
account_offset_and_flags: AccountOffsetAndFlags,
|
|
}
|
|
|
|
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub struct AccountOffsetAndFlags {
|
|
/// offset = 'packed_offset_and_flags.offset_reduced()' * ALIGN_BOUNDARY_OFFSET into the storage
|
|
/// Note this is a smaller type than 'Offset'
|
|
packed_offset_and_flags: PackedOffsetAndFlags,
|
|
}
|
|
|
|
impl ZeroLamport for AccountInfo {
|
|
fn is_zero_lamport(&self) -> bool {
|
|
self.account_offset_and_flags
|
|
.packed_offset_and_flags
|
|
.is_zero_lamport()
|
|
}
|
|
}
|
|
|
|
impl IsCached for AccountInfo {
|
|
fn is_cached(&self) -> bool {
|
|
self.account_offset_and_flags
|
|
.packed_offset_and_flags
|
|
.offset_reduced()
|
|
== CACHED_OFFSET
|
|
}
|
|
}
|
|
|
|
impl IsCached for StorageLocation {
|
|
fn is_cached(&self) -> bool {
|
|
matches!(self, StorageLocation::Cached)
|
|
}
|
|
}
|
|
|
|
/// We have to have SOME value for store_id when we are cached
|
|
const CACHE_VIRTUAL_STORAGE_ID: AppendVecId = AppendVecId::MAX;
|
|
|
|
impl AccountInfo {
|
|
pub fn new(storage_location: StorageLocation, lamports: u64) -> Self {
|
|
let mut packed_offset_and_flags = PackedOffsetAndFlags::default();
|
|
let store_id = match storage_location {
|
|
StorageLocation::AppendVec(store_id, offset) => {
|
|
let reduced_offset = Self::get_reduced_offset(offset);
|
|
assert_ne!(
|
|
CACHED_OFFSET, reduced_offset,
|
|
"illegal offset for non-cached item"
|
|
);
|
|
packed_offset_and_flags.set_offset_reduced(Self::get_reduced_offset(offset));
|
|
assert_eq!(
|
|
Self::reduced_offset_to_offset(packed_offset_and_flags.offset_reduced()),
|
|
offset,
|
|
"illegal offset"
|
|
);
|
|
store_id
|
|
}
|
|
StorageLocation::Cached => {
|
|
packed_offset_and_flags.set_offset_reduced(CACHED_OFFSET);
|
|
CACHE_VIRTUAL_STORAGE_ID
|
|
}
|
|
};
|
|
packed_offset_and_flags.set_is_zero_lamport(lamports == 0);
|
|
let account_offset_and_flags = AccountOffsetAndFlags {
|
|
packed_offset_and_flags,
|
|
};
|
|
Self {
|
|
store_id,
|
|
account_offset_and_flags,
|
|
}
|
|
}
|
|
|
|
fn get_reduced_offset(offset: usize) -> OffsetReduced {
|
|
(offset / ALIGN_BOUNDARY_OFFSET) as OffsetReduced
|
|
}
|
|
|
|
pub fn store_id(&self) -> AppendVecId {
|
|
// if the account is in a cached store, the store_id is meaningless
|
|
assert!(!self.is_cached());
|
|
self.store_id
|
|
}
|
|
|
|
pub fn offset(&self) -> Offset {
|
|
Self::reduced_offset_to_offset(
|
|
self.account_offset_and_flags
|
|
.packed_offset_and_flags
|
|
.offset_reduced(),
|
|
)
|
|
}
|
|
|
|
fn reduced_offset_to_offset(reduced_offset: OffsetReduced) -> Offset {
|
|
(reduced_offset as Offset) * ALIGN_BOUNDARY_OFFSET
|
|
}
|
|
|
|
pub fn storage_location(&self) -> StorageLocation {
|
|
if self.is_cached() {
|
|
StorageLocation::Cached
|
|
} else {
|
|
StorageLocation::AppendVec(self.store_id, self.offset())
|
|
}
|
|
}
|
|
}
|
|
#[cfg(test)]
|
|
mod test {
|
|
use {super::*, crate::append_vec::MAXIMUM_APPEND_VEC_FILE_SIZE};
|
|
|
|
#[test]
|
|
fn test_limits() {
|
|
for offset in [
|
|
// MAXIMUM_APPEND_VEC_FILE_SIZE is too big. That would be an offset at the first invalid byte in the max file size.
|
|
// MAXIMUM_APPEND_VEC_FILE_SIZE - 8 bytes would reference the very last 8 bytes in the file size. It makes no sense to reference that since element sizes are always more than 8.
|
|
// MAXIMUM_APPEND_VEC_FILE_SIZE - 16 bytes would reference the second to last 8 bytes in the max file size. This is still likely meaningless, but it is 'valid' as far as the index
|
|
// is concerned.
|
|
(MAXIMUM_APPEND_VEC_FILE_SIZE - 2 * (ALIGN_BOUNDARY_OFFSET as u64)) as Offset,
|
|
0,
|
|
ALIGN_BOUNDARY_OFFSET,
|
|
4 * ALIGN_BOUNDARY_OFFSET,
|
|
] {
|
|
let info = AccountInfo::new(StorageLocation::AppendVec(0, offset), 0);
|
|
assert!(info.offset() == offset);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "illegal offset")]
|
|
fn test_illegal_offset() {
|
|
let offset = (MAXIMUM_APPEND_VEC_FILE_SIZE - (ALIGN_BOUNDARY_OFFSET as u64)) as Offset;
|
|
AccountInfo::new(StorageLocation::AppendVec(0, offset), 0);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "illegal offset")]
|
|
fn test_alignment() {
|
|
let offset = 1; // not aligned
|
|
AccountInfo::new(StorageLocation::AppendVec(0, offset), 0);
|
|
}
|
|
}
|