use std::{
collections::HashMap,
sync::{Arc, OnceLock},
};
use parking_lot::RwLock;
use solana_address::Address;
use solana_program_runtime::program_cache_entry::ProgramCacheEntry;
pub(crate) type DefaultProgramKey = (usize, usize, usize, Address);
pub(crate) static DEFAULT_PROGRAM_CACHE: OnceLock<
RwLock<HashMap<DefaultProgramKey, Arc<ProgramCacheEntry>>>,
> = OnceLock::new();
pub(crate) fn default_program_cache()
-> &'static RwLock<HashMap<DefaultProgramKey, Arc<ProgramCacheEntry>>> {
DEFAULT_PROGRAM_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ProgramEntryKey {
pub(crate) env: usize,
pub(crate) hash: [u8; 32],
pub(crate) loader: Address,
pub(crate) slot_bucket: u64,
}
impl ProgramEntryKey {
pub(crate) const fn new(env: usize, hash: [u8; 32], loader: Address, slot_bucket: u64) -> Self {
Self { env, hash, loader, slot_bucket }
}
}
#[must_use]
pub(crate) fn content_hash(elf_bytes: &[u8]) -> [u8; 32] {
solana_sha256_hasher::hashv(&[elf_bytes]).to_bytes()
}
#[must_use]
pub(crate) fn content_key(
env_ptr: usize,
elf_bytes: &[u8],
loader: &Address,
slot_bucket: u64,
) -> ProgramEntryKey {
ProgramEntryKey::new(env_ptr, content_hash(elf_bytes), *loader, slot_bucket)
}
pub(crate) struct ProgramEntryStore {
inner: RwLock<HashMap<ProgramEntryKey, Arc<ProgramCacheEntry>>>,
}
impl ProgramEntryStore {
pub(crate) fn new() -> Self {
Self { inner: RwLock::new(HashMap::new()) }
}
pub(crate) fn get(&self, key: &ProgramEntryKey) -> Option<Arc<ProgramCacheEntry>> {
self.inner.read().get(key).cloned()
}
pub(crate) fn insert(
&self,
key: ProgramEntryKey,
entry: Arc<ProgramCacheEntry>,
) -> Arc<ProgramCacheEntry> {
self.inner.write().entry(key).or_insert(entry).clone()
}
pub(crate) fn len(&self) -> usize {
self.inner.read().len()
}
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool {
self.inner.read().is_empty()
}
#[cfg(test)]
pub(crate) fn clear(&self) {
self.inner.write().clear();
}
}
impl Default for ProgramEntryStore {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for ProgramEntryStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProgramEntryStore").field("len", &self.len()).finish_non_exhaustive()
}
}
static PROGRAM_ENTRY_STORE: OnceLock<ProgramEntryStore> = OnceLock::new();
pub(crate) fn program_entry_store() -> &'static ProgramEntryStore {
PROGRAM_ENTRY_STORE.get_or_init(ProgramEntryStore::new)
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
#[test]
fn content_hash_is_deterministic_and_sensitive_to_one_byte() {
let a = vec![1u8, 2, 3, 4];
let mut b = a.clone();
b[2] = 9;
assert_eq!(content_hash(&a), content_hash(&a));
assert_ne!(content_hash(&a), content_hash(&b));
}
#[test]
fn key_is_deterministic_for_fixed_tuple() {
let loader = Address::new_unique();
let bytes = vec![7u8; 64];
let k1 = content_key(0x1234, &bytes, &loader, u64::MAX);
let k2 = content_key(0x1234, &bytes, &loader, u64::MAX);
assert_eq!(k1, k2);
let mut mutated = bytes.clone();
mutated[0] ^= 0xff;
let k3 = content_key(0x1234, &mutated, &loader, u64::MAX);
assert_ne!(k1, k3);
let k4 = content_key(0x5678, &bytes, &loader, u64::MAX);
assert_ne!(k1, k4);
}
#[test]
fn store_insert_is_first_writer_wins() {
let store = ProgramEntryStore::new();
let loader = Address::new_unique();
let key = content_key(1, &[1, 2, 3], &loader, 0);
assert!(store.get(&key).is_none());
assert_eq!(store.len(), 0);
assert!(store.is_empty());
}
#[test]
fn default_program_cache_returns_a_stable_singleton() {
assert!(std::ptr::eq(default_program_cache(), default_program_cache()));
}
#[test]
fn isolated_store_get_and_len_observe_inserts() {
use solana_program_runtime::{
loaded_programs::ProgramRuntimeEnvironment, solana_sbpf::program::BuiltinProgram,
};
use crate::programs::load_program_uncompiled;
let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_loader(Default::default()));
let elf = include_bytes!("../../elf/spl_memo-3.0.0.so");
let loader = solana_sdk_ids::bpf_loader::id();
let entry =
Arc::new(load_program_uncompiled(&loader, &env, elf).expect("load should succeed"));
let store = ProgramEntryStore::new();
let key = content_key(0xabc, elf, &loader, crate::accounts_db::IMMEDIATE_DEPLOYMENT_SLOT);
assert_eq!(store.len(), 0);
let resident = store.insert(key, Arc::clone(&entry));
assert!(Arc::ptr_eq(&resident, &entry), "first insert must win");
assert_eq!(store.len(), 1);
let hit = store.get(&key).expect("inserted entry must be retrievable");
assert!(Arc::ptr_eq(&hit, &entry));
}
proptest! {
#[test]
fn key_is_deterministic_for_arbitrary_inputs(
bytes in prop::collection::vec(any::<u8>(), 0..512),
env in any::<usize>(),
bucket in any::<u64>(),
loader_seed in any::<[u8; 32]>(),
) {
let loader = Address::new_from_array(loader_seed);
let k1 = content_key(env, &bytes, &loader, bucket);
let k2 = content_key(env, &bytes, &loader, bucket);
prop_assert_eq!(k1, k2);
}
#[test]
fn differing_bytes_never_collide(
base in prop::collection::vec(any::<u8>(), 1..512),
flip_at in 0usize..512,
flip_to in any::<u8>(),
) {
let idx = flip_at % base.len();
let mut mutated = base.clone();
prop_assume!(mutated[idx] != flip_to);
mutated[idx] = flip_to;
let loader = Address::new_unique();
let k1 = content_key(0x1234, &base, &loader, u64::MAX);
let k2 = content_key(0x1234, &mutated, &loader, u64::MAX);
prop_assert_ne!(k1, k2);
}
}
}