diff --git a/evm_loader/lib/src/account_storage_tests.rs b/evm_loader/lib/src/account_storage_tests.rs index 5445f3e15..98a31fc4f 100644 --- a/evm_loader/lib/src/account_storage_tests.rs +++ b/evm_loader/lib/src/account_storage_tests.rs @@ -4,24 +4,24 @@ use crate::tracing::AccountOverride; use evm_loader::types::vector::VectorVecExt; use hex_literal::hex; use solana_account_decoder::UiDataSliceConfig; + use std::collections::HashMap; use std::str::FromStr; - const STORAGE_LENGTH: usize = 32 * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT; mod mock_rpc_client { + use crate::commands::get_config::BuildConfigSimulator; use crate::NeonResult; use crate::{commands::get_config::ConfigSimulator, rpc::Rpc}; use async_trait::async_trait; + use solana_account_decoder::UiDataSliceConfig; use solana_client::client_error::Result as ClientResult; use solana_sdk::account::Account; use solana_sdk::clock::{Slot, UnixTimestamp}; use solana_sdk::pubkey::Pubkey; use std::collections::HashMap; - use solana_account_decoder::UiDataSliceConfig; - pub struct MockRpcClient { accounts: HashMap, } @@ -91,7 +91,7 @@ mod mock_rpc_client { #[async_trait(?Send)] impl BuildConfigSimulator for MockRpcClient { - fn use_cache(&self) -> bool { + fn use_cache_for_chains(&self) -> bool { false } async fn build_config_simulator(&self, _program_id: Pubkey) -> NeonResult { diff --git a/evm_loader/lib/src/commands/get_config.rs b/evm_loader/lib/src/commands/get_config.rs index f67108b7d..35097ba49 100644 --- a/evm_loader/lib/src/commands/get_config.rs +++ b/evm_loader/lib/src/commands/get_config.rs @@ -2,21 +2,23 @@ use std::collections::BTreeMap; +use crate::rpc::{CallDbClient, CloneRpcClient, Rpc}; +use crate::solana_simulator::SolanaSimulator; +use crate::types::programs_cache::KeyAccountCache; +use crate::types::programs_cache::{program_config_cache_add, program_config_cache_get}; use async_trait::async_trait; use base64::Engine; use enum_dispatch::enum_dispatch; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; +pub use solana_account_decoder::UiDataSliceConfig as SliceConfig; use solana_client::rpc_config::RpcSimulateTransactionConfig; use solana_sdk::signer::Signer; use solana_sdk::{instruction::Instruction, pubkey::Pubkey, transaction::Transaction}; -use tokio::sync::OnceCell; -use crate::rpc::{CallDbClient, CloneRpcClient}; -use crate::solana_simulator::SolanaSimulator; use crate::NeonResult; - -#[derive(Debug, Serialize, Deserialize)] +use tokio::sync::OnceCell; +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum Status { Ok, Emergency, @@ -33,7 +35,7 @@ pub struct ChainInfo { } #[serde_as] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetConfigResponse { pub version: String, pub revision: String, @@ -57,17 +59,52 @@ pub enum ConfigSimulator<'r> { #[async_trait(?Send)] #[enum_dispatch] -pub trait BuildConfigSimulator { - fn use_cache(&self) -> bool; +pub trait BuildConfigSimulator: Rpc { + fn use_cache_for_chains(&self) -> bool; + async fn get_config(&self, program_id: Pubkey) -> NeonResult { + let maybe_slot = self.get_last_deployed_slot(&program_id).await?; + if let Some(slot) = maybe_slot { + let key = KeyAccountCache { + addr: program_id, + slot, + }; + + let rz = program_config_cache_get(&key).await; + if rz.is_some() { + return Ok(rz.unwrap()); + }; + } + let mut simulator = self.build_config_simulator(program_id).await?; + + let (version, revision) = simulator.get_version().await?; + + let result = GetConfigResponse { + version, + revision, + status: simulator.get_status().await?, + environment: simulator.get_environment().await?, + chains: simulator.get_chains().await?, + config: simulator.get_properties().await?, + }; + if let Some(slot) = maybe_slot { + let key = KeyAccountCache { + addr: program_id, + slot, + }; + program_config_cache_add(key, result.clone()).await; + } + + Ok(result) + } + async fn build_config_simulator(&self, program_id: Pubkey) -> NeonResult; } #[async_trait(?Send)] impl BuildConfigSimulator for CloneRpcClient { - fn use_cache(&self) -> bool { + fn use_cache_for_chains(&self) -> bool { true } - async fn build_config_simulator(&self, program_id: Pubkey) -> NeonResult { Ok(ConfigSimulator::CloneRpcClient { program_id, @@ -78,13 +115,13 @@ impl BuildConfigSimulator for CloneRpcClient { #[async_trait(?Send)] impl BuildConfigSimulator for CallDbClient { - fn use_cache(&self) -> bool { + fn use_cache_for_chains(&self) -> bool { false } - async fn build_config_simulator(&self, program_id: Pubkey) -> NeonResult { let mut simulator = SolanaSimulator::new_without_sync(self).await?; simulator.sync_accounts(self, &[program_id]).await?; + Ok(ConfigSimulator::ProgramTestContext { program_id, simulator, @@ -259,49 +296,31 @@ impl ConfigSimulator<'_> { Ok(result) } } - +static CHAINS_CACHE: OnceCell> = OnceCell::const_new(); pub async fn execute( rpc: &impl BuildConfigSimulator, program_id: Pubkey, ) -> NeonResult { - let mut simulator = rpc.build_config_simulator(program_id).await?; - - let (version, revision) = simulator.get_version().await?; - - Ok(GetConfigResponse { - version, - revision, - status: simulator.get_status().await?, - environment: simulator.get_environment().await?, - chains: simulator.get_chains().await?, - config: simulator.get_properties().await?, - }) + rpc.get_config(program_id).await } -static CHAINS_CACHE: OnceCell> = OnceCell::const_new(); - pub async fn read_chains( rpc: &impl BuildConfigSimulator, program_id: Pubkey, ) -> NeonResult> { - if rpc.use_cache() { - return CHAINS_CACHE - .get_or_try_init(|| get_chains(rpc, program_id)) + if rpc.use_cache_for_chains() { + let result = CHAINS_CACHE + .get_or_init(|| async { + rpc.get_config(program_id) + .await + .expect(" get config error for chain info") + .chains + }) .await - .cloned(); + .clone(); + return Ok(result); } - - get_chains(rpc, program_id).await -} - -async fn get_chains( - rpc: &(impl BuildConfigSimulator + Sized), - program_id: Pubkey, -) -> NeonResult> { - rpc.build_config_simulator(program_id) - .await? - .get_chains() - .await + Ok(rpc.get_config(program_id).await?.chains) } async fn read_chain_id( diff --git a/evm_loader/lib/src/rpc/mod.rs b/evm_loader/lib/src/rpc/mod.rs index d14e1763c..a909a5692 100644 --- a/evm_loader/lib/src/rpc/mod.rs +++ b/evm_loader/lib/src/rpc/mod.rs @@ -1,17 +1,16 @@ mod db_call_client; mod emulator_client; mod validator_client; - -pub use db_call_client::CallDbClient; -pub use validator_client::CloneRpcClient; - +use crate::commands::get_config::GetConfigResponse; use crate::commands::get_config::{BuildConfigSimulator, ConfigSimulator}; use crate::{NeonError, NeonResult}; use async_trait::async_trait; +pub use db_call_client::CallDbClient; use enum_dispatch::enum_dispatch; +use evm_loader::solana_program::bpf_loader_upgradeable::UpgradeableLoaderState; pub use solana_account_decoder::UiDataSliceConfig as SliceConfig; use solana_cli::cli::CliError; -use solana_client::client_error::Result as ClientResult; +use solana_client::client_error::{ClientErrorKind, Result as ClientResult}; use solana_sdk::{ account::Account, clock::{Slot, UnixTimestamp}, @@ -19,6 +18,7 @@ use solana_sdk::{ native_token::lamports_to_sol, pubkey::Pubkey, }; +pub use validator_client::CloneRpcClient; #[async_trait(?Send)] #[enum_dispatch] @@ -28,6 +28,20 @@ pub trait Rpc { key: &Pubkey, slice: Option, ) -> ClientResult>; + + async fn get_last_deployed_slot(&self, program_id: &Pubkey) -> ClientResult> { + let slice = SliceConfig { + offset: 0, + length: UpgradeableLoaderState::size_of_programdata_metadata(), + }; + let result = self.get_account_slice(program_id, Some(slice)).await; + if let Ok(Some(acc)) = result { + let slot = get_programdata_slot_from_account(&acc)?; + return Ok(slot); + } + Err(ClientErrorKind::Custom("Not account on slot ".to_string()).into()) + } + async fn get_account(&self, key: &Pubkey) -> ClientResult> { self.get_account_slice(key, None).await } @@ -61,6 +75,7 @@ macro_rules! e { }; } +use crate::types::programs_cache::get_programdata_slot_from_account; pub(crate) use e; pub(crate) async fn check_account_for_fee( diff --git a/evm_loader/lib/src/types/programs_cache.rs b/evm_loader/lib/src/types/programs_cache.rs index a438b5b60..a72a9fbef 100644 --- a/evm_loader/lib/src/types/programs_cache.rs +++ b/evm_loader/lib/src/types/programs_cache.rs @@ -2,9 +2,10 @@ use crate::rpc::Rpc; use async_trait::async_trait; +use crate::commands::get_config::GetConfigResponse; use bincode::deserialize; use futures::future::join_all; -use solana_client::client_error::{ClientErrorKind, Result as ClientResult}; +use solana_client::client_error::Result as ClientResult; use solana_sdk::{ account::Account, bpf_loader_upgradeable::UpgradeableLoaderState, @@ -18,17 +19,52 @@ use std::sync::RwLock; use bincode::serialize; use tokio::sync::OnceCell; use tracing::info; -#[derive(Debug, Eq, PartialEq, Hash)] + +use crate::rpc::SliceConfig; +#[derive(Debug, Eq, PartialEq, Hash, Clone)] pub struct KeyAccountCache { - addr: Pubkey, - slot: u64, + pub addr: Pubkey, + pub slot: u64, } -use crate::rpc::SliceConfig; -type ProgramDataCache = HashMap; -type ThreadSaveProgramDataCache = RwLock; +type ProgramDataCache = HashMap; + +struct ThreadSaveCache +where + Value: Clone, +{ + table: RwLock>, +} +impl ThreadSaveCache +where + Value: Clone, +{ + pub fn new() -> Self { + Self { + table: RwLock::new(HashMap::new()), + } + } + + fn get(&self, key: &KeyAccountCache) -> Option { + self.table + .read() + .expect("lock on read error ") + .get(key) + .cloned() + } + fn add(&self, key: KeyAccountCache, value: Value) { + self.table + .write() + .expect("lock on write error ") + .insert(key, value); + } +} + +type ThreadSaveProgramDataCache = ThreadSaveCache; +type ThreadSaveConfigCache = ThreadSaveCache; -static LOCAL_CONFIG: OnceCell = OnceCell::const_new(); +static ACCOUNT_CACHE_TABLE: OnceCell = OnceCell::const_new(); +static CONFIG_CACHE_TABLE: OnceCell = OnceCell::const_new(); pub async fn cut_programdata_from_acc(account: &mut Account, data_slice: SliceConfig) { if data_slice.offset != 0 { @@ -39,45 +75,31 @@ pub async fn cut_programdata_from_acc(account: &mut Account, data_slice: SliceCo account.data.truncate(data_slice.length); } -async fn programdata_hash_get_instance() -> &'static ThreadSaveProgramDataCache { - LOCAL_CONFIG - .get_or_init(|| async { - let map = HashMap::new(); - - RwLock::new(map) - }) +async fn programdata_account_cache_get_instance() -> &'static ThreadSaveProgramDataCache { + ACCOUNT_CACHE_TABLE + .get_or_init(|| async { ThreadSaveProgramDataCache::new() }) .await } -async fn programdata_hash_get(addr: Pubkey, slot: u64) -> Option { - let val = KeyAccountCache { addr, slot }; - programdata_hash_get_instance() - .await - .read() - .expect("acc_hash_get_instance poisoned") - .get(&val) - .cloned() +async fn programdata_account_cache_get(addr: Pubkey, slot: u64) -> Option { + let key = KeyAccountCache { addr, slot }; + programdata_account_cache_get_instance().await.get(&key) } -async fn programdata_hash_add(addr: Pubkey, slot: u64, acc: Account) { - let val = KeyAccountCache { addr, slot }; - programdata_hash_get_instance() - .await - .write() - .expect("PANIC, no nable") - .insert(val, acc); +async fn programdata_account_cache_add(addr: Pubkey, slot: u64, acc: Account) { + let key = KeyAccountCache { addr, slot }; + programdata_account_cache_get_instance().await.add(key, acc); } -fn get_programdata_slot_from_account(acc: &Account) -> ClientResult { +/// in case of Not upgradeable account - return option None +pub fn get_programdata_slot_from_account(acc: &Account) -> ClientResult> { if !bpf_loader_upgradeable::check_id(&acc.owner) { - return Err(ClientErrorKind::Custom("Not upgradeable account".to_string()).into()); + return Ok(None); } match deserialize::(&acc.data) { - Ok(UpgradeableLoaderState::ProgramData { slot, .. }) => Ok(slot), - Ok(_) => { - panic!("Account is not of type `ProgramData`."); - } + Ok(UpgradeableLoaderState::ProgramData { slot, .. }) => Ok(Some(slot)), + Ok(_) => Ok(None), Err(e) => { eprintln!("Error occurred: {e:?}"); panic!("Failed to deserialize account data."); @@ -111,14 +133,18 @@ pub async fn programdata_cache_get_values_by_keys( for (result, key) in results.iter().zip(programdata_keys) { match result { Ok(Some(account)) => { - let slot_val = get_programdata_slot_from_account(account)?; - if let Some(acc) = programdata_hash_get(*key, slot_val).await { - answer.push(Some(acc)); - } else if let Ok(Some(tmp_acc)) = rpc.get_account(key).await { - let current_slot = get_programdata_slot_from_account(&tmp_acc)?; - programdata_hash_add(*key, current_slot, tmp_acc.clone()).await; - - answer.push(Some(tmp_acc)); + if let Some(slot_val) = get_programdata_slot_from_account(account)? { + if let Some(acc) = programdata_account_cache_get(*key, slot_val).await { + answer.push(Some(acc)); + } else if let Ok(Some(tmp_acc)) = rpc.get_account(key).await { + let current_slot = + get_programdata_slot_from_account(&tmp_acc)?.expect("No current slot "); + programdata_account_cache_add(*key, current_slot, tmp_acc.clone()).await; + + answer.push(Some(tmp_acc)); + } else { + answer.push(None); + } } else { answer.push(None); } @@ -169,6 +195,20 @@ impl FakeRpc { } } +async fn program_config_cache_get_instance() -> &'static ThreadSaveConfigCache { + CONFIG_CACHE_TABLE + .get_or_init(|| async { ThreadSaveConfigCache::new() }) + .await +} + +pub async fn program_config_cache_get(key: &KeyAccountCache) -> Option { + program_config_cache_get_instance().await.get(key) +} + +pub async fn program_config_cache_add(key: KeyAccountCache, val: GetConfigResponse) { + program_config_cache_get_instance().await.add(key, val); +} + #[async_trait(?Send)] impl Rpc for FakeRpc { @@ -278,4 +318,87 @@ mod tests { assert!(multiple_accounts[i].is_some(), "BAD ACC"); } } + + #[test] + fn test_create_new_cache() { + let cache: ThreadSaveCache = ThreadSaveCache::new(); + assert!(cache + .get(&KeyAccountCache { + slot: 0, + addr: Pubkey::new_unique(), + }) + .is_none()); + } + + #[test] + fn test_add_and_get_value() { + let cache: ThreadSaveCache = ThreadSaveCache::new(); + let key = KeyAccountCache { + slot: 0, + addr: Pubkey::new_unique(), + }; + let value = "test_value".to_string(); + + // Add the value to the cache + cache.add(key.clone(), value.clone()); + + // Retrieve the value from the cache + let result = cache.get(&key); + assert!(result.is_some()); + assert_eq!(result.unwrap(), value); + } + + #[test] + fn test_get_nonexistent_key() { + let cache: ThreadSaveCache = ThreadSaveCache::new(); + let key = KeyAccountCache { + slot: 0, + addr: Pubkey::new_unique(), + }; + + // Attempt to get a value for a key that doesn't exist + assert!(cache.get(&key).is_none()); + } + + #[test] + fn test_overwrite_existing_key() { + let cache: ThreadSaveCache = ThreadSaveCache::new(); + let key = KeyAccountCache { + slot: 0, + addr: Pubkey::new_unique(), + }; + let value1 = "value1".to_string(); + let value2 = "value2".to_string(); + + // Add the first value + cache.add(key.clone(), value1.clone()); + assert_eq!(cache.get(&key).unwrap(), value1); + + // Overwrite with the second value + cache.add(key.clone(), value2.clone()); + assert_eq!(cache.get(&key).unwrap(), value2); + } + + #[test] + fn test_multiple_keys() { + let cache: ThreadSaveCache = ThreadSaveCache::new(); + let key1 = KeyAccountCache { + slot: 0, + addr: Pubkey::new_unique(), + }; + let value1 = "value1".to_string(); + let key2 = KeyAccountCache { + slot: 0, + addr: Pubkey::new_unique(), + }; + let value2 = "value2".to_string(); + + // Add multiple key-value pairs + cache.add(key1.clone(), value1.clone()); + cache.add(key2.clone(), value2.clone()); + + // Check values for both keys + assert_eq!(cache.get(&key1).unwrap(), value1); + assert_eq!(cache.get(&key2).unwrap(), value2); + } }