From d2a6dda17a26d5b2695701cfa8c325a9d700923d Mon Sep 17 00:00:00 2001 From: rnovikov Date: Mon, 25 Nov 2024 13:03:10 +0400 Subject: [PATCH] Ndev 3385 Caching ProgramData accounts (#569) * Hash , rpc + db_work * fix clippy errors * test fixing * apply iface fixes for rocksdb * account check * add a unit test and mock rpc * fixed RPC mock and add unit test * applied mr comments * change rpc API && revert clickhouse job --- evm_loader/Cargo.lock | 1 + evm_loader/lib/Cargo.toml | 1 + evm_loader/lib/src/account_storage_tests.rs | 61 ++++- evm_loader/lib/src/commands/get_config.rs | 1 - evm_loader/lib/src/rpc/db_call_client.rs | 20 +- evm_loader/lib/src/rpc/emulator_client.rs | 34 ++- evm_loader/lib/src/rpc/mod.rs | 15 +- evm_loader/lib/src/rpc/validator_client.rs | 11 +- evm_loader/lib/src/solana_simulator/mod.rs | 7 +- evm_loader/lib/src/types/mod.rs | 4 + evm_loader/lib/src/types/programs_cache.rs | 281 ++++++++++++++++++++ evm_loader/lib/src/types/tracer_ch_db.rs | 54 ++-- evm_loader/lib/src/types/tracer_rocks_db.rs | 6 +- 13 files changed, 451 insertions(+), 45 deletions(-) create mode 100644 evm_loader/lib/src/types/programs_cache.rs diff --git a/evm_loader/Cargo.lock b/evm_loader/Cargo.lock index 6a3181ba0..50bbea9ec 100644 --- a/evm_loader/Cargo.lock +++ b/evm_loader/Cargo.lock @@ -3609,6 +3609,7 @@ dependencies = [ "enum_dispatch", "ethnum", "evm-loader", + "futures", "goblin 0.8.2", "hex", "hex-literal", diff --git a/evm_loader/lib/Cargo.toml b/evm_loader/lib/Cargo.toml index cfa1b8afe..f5cba1dd8 100644 --- a/evm_loader/lib/Cargo.toml +++ b/evm_loader/lib/Cargo.toml @@ -52,6 +52,7 @@ clap = "2.34.0" lazy_static = "1.5.0" elsa = "1.10.0" arrayref = "0.3.8" +futures = "0.3.30" [dev-dependencies] hex-literal = "0.4.1" diff --git a/evm_loader/lib/src/account_storage_tests.rs b/evm_loader/lib/src/account_storage_tests.rs index d5f68dc50..5445f3e15 100644 --- a/evm_loader/lib/src/account_storage_tests.rs +++ b/evm_loader/lib/src/account_storage_tests.rs @@ -3,6 +3,7 @@ use crate::rpc; 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; @@ -19,6 +20,8 @@ mod mock_rpc_client { use solana_sdk::pubkey::Pubkey; use std::collections::HashMap; + use solana_account_decoder::UiDataSliceConfig; + pub struct MockRpcClient { accounts: HashMap, } @@ -33,7 +36,31 @@ mod mock_rpc_client { #[async_trait(?Send)] impl Rpc for MockRpcClient { - async fn get_account(&self, key: &Pubkey) -> ClientResult> { + async fn get_account_slice( + &self, + key: &Pubkey, + slice: Option, + ) -> ClientResult> { + if let Some(data_slice) = slice { + if let Some(orig_acc) = self.accounts.get(key) { + let cut_to = + usize::min(data_slice.offset + data_slice.length, orig_acc.data.len()); + let sliced_data = if data_slice.offset < orig_acc.data.len() { + orig_acc.data[data_slice.offset..cut_to].to_vec() + } else { + vec![] + }; + + return Ok(Some(Account { + lamports: orig_acc.lamports, + data: sliced_data, + owner: orig_acc.owner, + executable: orig_acc.executable, + rent_epoch: orig_acc.rent_epoch, + })); + } + } + let result = self.accounts.get(key).cloned(); Ok(result) } @@ -1790,3 +1817,35 @@ async fn test_storage_new_from_other_and_override() { expected_balance ); } + +#[tokio::test] +async fn test_storage_get_account_slice() { + let slice_from = 2; + let slice_size = 20; + let test_key = Pubkey::new_unique(); + let acc = Account::new(10, 1 * 1024 * 1024, &solana_sdk::sysvar::rent::id()); + + let account_tuple = (test_key, acc); + let accounts_for_rpc = vec![ + (solana_sdk::sysvar::rent::id(), account_tuple.1.clone()), + account_tuple.clone(), + ]; + let rpc_client = mock_rpc_client::MockRpcClient::new(&accounts_for_rpc); + let acc_no_slice = rpc_client + .get_account(&test_key) + .await + .expect("Failed to get account slice"); + + let slice_cfg = UiDataSliceConfig { + offset: slice_from, + length: slice_size, + }; + let sliced_acc = rpc_client + .get_account_slice(&test_key, Some(slice_cfg)) + .await + .expect("Failed to get account slice"); + assert!(acc_no_slice.is_some()); + assert!(sliced_acc.is_some()); + assert!(acc_no_slice.unwrap().data.len() > 2000); + assert_eq!(sliced_acc.unwrap().data.len(), slice_size); +} diff --git a/evm_loader/lib/src/commands/get_config.rs b/evm_loader/lib/src/commands/get_config.rs index 0cd59b5d5..9fde6a2f6 100644 --- a/evm_loader/lib/src/commands/get_config.rs +++ b/evm_loader/lib/src/commands/get_config.rs @@ -85,7 +85,6 @@ impl BuildConfigSimulator for CallDbClient { 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, diff --git a/evm_loader/lib/src/rpc/db_call_client.rs b/evm_loader/lib/src/rpc/db_call_client.rs index 32e97b9d5..5664b09b3 100644 --- a/evm_loader/lib/src/rpc/db_call_client.rs +++ b/evm_loader/lib/src/rpc/db_call_client.rs @@ -1,4 +1,4 @@ -use super::{e, Rpc}; +use super::{e, Rpc, SliceConfig}; use crate::types::{TracerDb, TracerDbTrait}; use crate::NeonError; use crate::NeonError::RocksDb; @@ -42,9 +42,13 @@ impl CallDbClient { }) } - async fn get_account_at(&self, key: &Pubkey) -> ClientResult> { + async fn get_account_at( + &self, + key: &Pubkey, + slice: Option, + ) -> ClientResult> { self.tracer_db - .get_account_at(key, self.slot, self.tx_index_in_block) + .get_account_at(key, self.slot, self.tx_index_in_block, slice) .await .map_err(|e| e!("load account error", key, e)) } @@ -52,8 +56,12 @@ impl CallDbClient { #[async_trait(?Send)] impl Rpc for CallDbClient { - async fn get_account(&self, key: &Pubkey) -> ClientResult> { - self.get_account_at(key).await + async fn get_account_slice( + &self, + key: &Pubkey, + slice: Option, + ) -> ClientResult> { + self.get_account_at(key, slice).await } async fn get_multiple_accounts( @@ -62,7 +70,7 @@ impl Rpc for CallDbClient { ) -> ClientResult>> { let mut result = Vec::new(); for key in pubkeys { - result.push(self.get_account_at(key).await?); + result.push(self.get_account_at(key, None).await?); } debug!("get_multiple_accounts: pubkeys={pubkeys:?} result={result:?}"); Ok(result) diff --git a/evm_loader/lib/src/rpc/emulator_client.rs b/evm_loader/lib/src/rpc/emulator_client.rs index 5a63af7f6..ae35c25ac 100644 --- a/evm_loader/lib/src/rpc/emulator_client.rs +++ b/evm_loader/lib/src/rpc/emulator_client.rs @@ -10,21 +10,35 @@ use solana_sdk::{ use crate::account_storage::{fake_operator, EmulatorAccountStorage}; -use super::Rpc; +use super::{Rpc, SliceConfig}; #[async_trait(?Send)] impl<'rpc, T: Rpc> Rpc for EmulatorAccountStorage<'rpc, T> { - async fn get_account(&self, key: &Pubkey) -> ClientResult> { - if *key == self.operator() { - return Ok(Some(fake_operator())); - } - - if let Some(account_data) = self.accounts_get(key) { - return Ok(Some(Account::from(&*account_data))); + async fn get_account_slice( + &self, + key: &Pubkey, + slice: Option, + ) -> ClientResult> { + let answer_account = if *key == self.operator() { + Some(fake_operator()) + } else if let Some(account_data) = self.accounts_get(key) { + Some(Account::from(&*account_data)) + } else { + self._get_account_from_rpc(*key).await?.cloned() + }; + + if let Some(data_slice) = slice { + // if only slice is necessary - cut data + if let Some(mut account) = answer_account { + if data_slice.offset != 0 { + account.data.drain(0..data_slice.offset); + } + account.data.truncate(data_slice.length); + return Ok(Some(account)); + } } - let account = self._get_account_from_rpc(*key).await?.cloned(); - Ok(account) + Ok(answer_account) } async fn get_multiple_accounts( diff --git a/evm_loader/lib/src/rpc/mod.rs b/evm_loader/lib/src/rpc/mod.rs index afa09bdd4..d14e1763c 100644 --- a/evm_loader/lib/src/rpc/mod.rs +++ b/evm_loader/lib/src/rpc/mod.rs @@ -9,20 +9,29 @@ use crate::commands::get_config::{BuildConfigSimulator, ConfigSimulator}; use crate::{NeonError, NeonResult}; use async_trait::async_trait; use enum_dispatch::enum_dispatch; +pub use solana_account_decoder::UiDataSliceConfig as SliceConfig; use solana_cli::cli::CliError; use solana_client::client_error::Result as ClientResult; -use solana_sdk::message::Message; -use solana_sdk::native_token::lamports_to_sol; use solana_sdk::{ account::Account, clock::{Slot, UnixTimestamp}, + message::Message, + native_token::lamports_to_sol, pubkey::Pubkey, }; #[async_trait(?Send)] #[enum_dispatch] pub trait Rpc { - async fn get_account(&self, key: &Pubkey) -> ClientResult>; + async fn get_account_slice( + &self, + key: &Pubkey, + slice: Option, + ) -> ClientResult>; + async fn get_account(&self, key: &Pubkey) -> ClientResult> { + self.get_account_slice(key, None).await + } + async fn get_multiple_accounts(&self, pubkeys: &[Pubkey]) -> ClientResult>>; async fn get_block_time(&self, slot: Slot) -> ClientResult; diff --git a/evm_loader/lib/src/rpc/validator_client.rs b/evm_loader/lib/src/rpc/validator_client.rs index 1c64a8623..e8a0585c9 100644 --- a/evm_loader/lib/src/rpc/validator_client.rs +++ b/evm_loader/lib/src/rpc/validator_client.rs @@ -1,6 +1,6 @@ use crate::{config::APIOptions, Config}; -use super::Rpc; +use super::{Rpc, SliceConfig}; use async_trait::async_trait; use solana_account_decoder::{UiAccount, UiAccountEncoding}; use solana_client::{ @@ -15,6 +15,7 @@ use solana_sdk::{ clock::{Slot, UnixTimestamp}, pubkey::Pubkey, }; + use std::{error::Error, ops::Deref, time::Duration}; use std::{future::Future, sync::Arc}; use tracing::debug; @@ -113,12 +114,16 @@ impl Deref for CloneRpcClient { #[async_trait(?Send)] impl Rpc for CloneRpcClient { - async fn get_account(&self, key: &Pubkey) -> ClientResult> { + async fn get_account_slice( + &self, + key: &Pubkey, + slice: Option, + ) -> ClientResult> { let request = || { let config = RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::Base64Zstd), commitment: Some(self.commitment()), - data_slice: None, + data_slice: slice, min_context_slot: None, }; let params = serde_json::json!([key.to_string(), config]); diff --git a/evm_loader/lib/src/solana_simulator/mod.rs b/evm_loader/lib/src/solana_simulator/mod.rs index 6be80bde4..5c0f4bb36 100644 --- a/evm_loader/lib/src/solana_simulator/mod.rs +++ b/evm_loader/lib/src/solana_simulator/mod.rs @@ -5,6 +5,7 @@ use std::sync::Arc; pub use error::Error; use evm_loader::solana_program::bpf_loader_upgradeable::UpgradeableLoaderState; use evm_loader::solana_program::clock::Slot; + use evm_loader::solana_program::loader_v4; use evm_loader::solana_program::loader_v4::{LoaderV4State, LoaderV4Status}; use evm_loader::solana_program::message::SanitizedMessage; @@ -51,6 +52,7 @@ use solana_sdk::{ pub use utils::SyncState; use crate::rpc::Rpc; +use crate::types::programs_cache::programdata_cache_get_values_by_keys; mod error; mod utils; @@ -113,7 +115,6 @@ impl SolanaSimulator { let Some(account) = account else { continue; }; - if account.executable && bpf_loader_upgradeable::check_id(&account.owner) { let programdata_address = utils::program_data_address(account)?; debug!( @@ -129,7 +130,9 @@ impl SolanaSimulator { storable_accounts.push((key, account)); } - let mut programdata_accounts = rpc.get_multiple_accounts(&programdata_keys).await?; + let mut programdata_accounts = + programdata_cache_get_values_by_keys(&programdata_keys, rpc).await?; + for (key, account) in programdata_keys.iter().zip(&mut programdata_accounts) { let Some(account) = account else { continue; diff --git a/evm_loader/lib/src/types/mod.rs b/evm_loader/lib/src/types/mod.rs index 0993d346b..fc8906043 100644 --- a/evm_loader/lib/src/types/mod.rs +++ b/evm_loader/lib/src/types/mod.rs @@ -1,5 +1,6 @@ pub mod tracer_ch_common; +pub mod programs_cache; pub(crate) mod tracer_ch_db; pub mod tracer_rocks_db; @@ -26,6 +27,8 @@ use evm_loader::{ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use serde_with::{hex::Hex, serde_as, DisplayFromStr, OneOrMany}; + +use crate::rpc::SliceConfig; use solana_sdk::signature::Signature; use solana_sdk::{account::Account, pubkey::Pubkey}; use std::collections::HashMap; @@ -79,6 +82,7 @@ pub trait TracerDbTrait { pubkey: &Pubkey, slot: u64, tx_index_in_block: Option, + data_slice: Option, ) -> DbResult>; async fn get_transaction_index(&self, signature: Signature) -> DbResult; diff --git a/evm_loader/lib/src/types/programs_cache.rs b/evm_loader/lib/src/types/programs_cache.rs new file mode 100644 index 000000000..a438b5b60 --- /dev/null +++ b/evm_loader/lib/src/types/programs_cache.rs @@ -0,0 +1,281 @@ +// use crate::tracing::tracers::state_diff::Account; +use crate::rpc::Rpc; +use async_trait::async_trait; + +use bincode::deserialize; +use futures::future::join_all; +use solana_client::client_error::{ClientErrorKind, Result as ClientResult}; +use solana_sdk::{ + account::Account, + bpf_loader_upgradeable::UpgradeableLoaderState, + clock::{Slot, UnixTimestamp}, + pubkey::Pubkey, +}; +use std::collections::HashMap; +use std::hash::Hash; +use std::sync::RwLock; + +use bincode::serialize; +use tokio::sync::OnceCell; +use tracing::info; +#[derive(Debug, Eq, PartialEq, Hash)] +pub struct KeyAccountCache { + addr: Pubkey, + slot: u64, +} + +use crate::rpc::SliceConfig; +type ProgramDataCache = HashMap; +type ThreadSaveProgramDataCache = RwLock; + +static LOCAL_CONFIG: OnceCell = OnceCell::const_new(); + +pub async fn cut_programdata_from_acc(account: &mut Account, data_slice: SliceConfig) { + if data_slice.offset != 0 { + account + .data + .drain(..std::cmp::min(account.data.len(), data_slice.offset)); + } + 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) + }) + .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_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); +} + +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()); + } + + match deserialize::(&acc.data) { + Ok(UpgradeableLoaderState::ProgramData { slot, .. }) => Ok(slot), + Ok(_) => { + panic!("Account is not of type `ProgramData`."); + } + Err(e) => { + eprintln!("Error occurred: {e:?}"); + panic!("Failed to deserialize account data."); + } + } +} + +pub async fn programdata_cache_get_values_by_keys( + programdata_keys: &Vec, + rpc: &impl Rpc, +) -> ClientResult>> { + let mut future_requests = Vec::new(); + let mut answer = Vec::new(); + + for key in programdata_keys { + future_requests.push(rpc.get_account_slice( + key, + Some(SliceConfig { + offset: 0, + length: UpgradeableLoaderState::size_of_programdata_metadata(), + }), + )); + } + + assert_eq!( + programdata_keys.len(), + future_requests.len(), + "programdata_keys.size()!=future_requests.size()" + ); + let results = join_all(future_requests).await; + 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)); + } else { + answer.push(None); + } + } + Ok(None) => { + info!("Account for key {key:?} is None."); + answer.push(None); + } + Err(e) => { + info!("Error fetching account for key {key:?}: {e:?}"); + } + } + } + Ok(answer) +} + +struct FakeRpc { + accounts: HashMap, +} +#[allow(dead_code)] +impl FakeRpc { + pub fn new() -> Self { + Self { + accounts: HashMap::new(), + } + } + + fn has_account(&self, pubkey: &Pubkey) -> bool { + self.accounts.contains_key(pubkey) + } + + fn make_account(&mut self, pubkey: Pubkey) -> Account { + // Define the slot number you want to test with + let test_slot: u64 = 42; + + // Create mock ProgramData state + let program_data = UpgradeableLoaderState::ProgramData { + slot: test_slot, + upgrade_authority_address: Some(Pubkey::new_unique()), + }; + let mut serialized_data = serialize(&program_data).unwrap(); + serialized_data.resize(4 * 1024 * 1024, 0); + let mut answer = Account::new(0, serialized_data.len(), &bpf_loader_upgradeable::id()); + answer.data = serialized_data; + + self.accounts.insert(pubkey, answer.clone()); + answer + } +} + +#[async_trait(?Send)] + +impl Rpc for FakeRpc { + async fn get_account_slice( + &self, + pubkey: &Pubkey, + slice: Option, + ) -> ClientResult> { + assert!(self.accounts.contains_key(pubkey), " "); + + let mut answer = self.accounts.get(pubkey).unwrap().clone(); + if let Some(data_slice) = slice { + if data_slice.offset != 0 { + answer + .data + .drain(..std::cmp::min(answer.data.len(), data_slice.offset)); + } + answer.data.truncate(data_slice.length); + } + Ok(Some(answer)) + } + + async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + ) -> ClientResult>> { + let mut futures = Vec::new(); + for pubkey in pubkeys { + futures.push(self.get_account(pubkey).await?); + } + + Ok(futures) + } + + async fn get_block_time(&self, _slot: Slot) -> ClientResult { + Ok(9999) + } + async fn get_slot(&self) -> ClientResult { + Ok(1212) + } + + async fn get_deactivated_solana_features(&self) -> ClientResult> { + Ok(Vec::new()) + } +} +use evm_loader::solana_program::bpf_loader_upgradeable; +use tokio; + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_acc_slice() { + let mut rpc = FakeRpc::new(); + let test_key = Pubkey::new_unique(); + + let test_acc = rpc.make_account(test_key); + if test_acc.data.len() >= 4000000 { + println!("Account data len: {}", test_acc.data.len()); + } else { + panic!("test stop"); + } + if let Ok(test2_acc) = rpc.get_account(&test_key).await { + assert_eq!( + test_acc.data.len(), + test2_acc.expect("test fail").data.len() + ); + } else { + panic!("fake rpc returned error"); + } + + let test3_acc = rpc + .get_account_slice( + &test_key, + Some(SliceConfig { + offset: 0, + length: 1024, + }), + ) + .await; + assert_eq!(1024, test3_acc.unwrap().expect("test fail").data.len()); + } + #[tokio::test] + async fn test_acc_request() { + const TEST_KEYS_COUNT: usize = 10; + let mut rpc = FakeRpc::new(); + let mut test_keys = Vec::new(); //Pubkey::new_unique(); + + for _i in 0..TEST_KEYS_COUNT { + let curr_key = Pubkey::new_unique(); + rpc.make_account(curr_key); + test_keys.push(curr_key); + } + + let multiple_accounts = rpc + .get_multiple_accounts(&test_keys) + .await + .expect("ERR DURING ACC REQUESTS"); + + let hashed_accounts = programdata_cache_get_values_by_keys(&test_keys, &rpc) + .await + .expect("ERR DURING ACC REQUESTS WITH HASH"); + assert_eq!(hashed_accounts.len(), multiple_accounts.len()); + for i in 0..TEST_KEYS_COUNT { + assert!(hashed_accounts[i].is_some(), "BAD ACC"); + assert!(multiple_accounts[i].is_some(), "BAD ACC"); + } + } +} diff --git a/evm_loader/lib/src/types/tracer_ch_db.rs b/evm_loader/lib/src/types/tracer_ch_db.rs index 15dddd982..818daf32d 100644 --- a/evm_loader/lib/src/types/tracer_ch_db.rs +++ b/evm_loader/lib/src/types/tracer_ch_db.rs @@ -5,14 +5,15 @@ use crate::{ }; use super::tracer_ch_common::{ChResult, EthSyncStatus, EthSyncing, RevisionMap, SlotParentRooted}; - use crate::account_data::AccountData; use crate::config::ChDbConfig; +use crate::types::programs_cache::cut_programdata_from_acc; use anyhow::anyhow; use async_trait::async_trait; use clickhouse::Client; use log::{debug, error, info}; use rand::Rng; +pub use solana_account_decoder::UiDataSliceConfig as SliceConfig; use solana_sdk::signature::Signature; use solana_sdk::{ account::Account, @@ -93,23 +94,19 @@ impl TracerDbTrait for ClickHouseDb { pubkey: &Pubkey, slot: u64, tx_index_in_block: Option, + data_slice: Option, ) -> DbResult> { - if let Some(tx_index_in_block) = tx_index_in_block { - return if let Some(account) = self - .get_account_at_index_in_block(pubkey, slot, tx_index_in_block) - .await? - { - Ok(Some(account)) - } else { - self.get_account_at_slot(pubkey, slot - 1) - .await - .map_err(|e| anyhow!("Failed to get NEON_REVISION, error: {e}")) - }; + let result = self + .get_full_account_at(pubkey, slot, tx_index_in_block) + .await; + if let Ok(Some(mut account)) = result { + if let Some(slice) = data_slice { + cut_programdata_from_acc(&mut account, slice).await; + } + Ok(Some(account)) + } else { + result } - - self.get_account_at_slot(pubkey, slot) - .await - .map_err(|e| anyhow!("Failed to get NEON_REVISION, error: {e}")) } async fn get_transaction_index(&self, signature: Signature) -> DbResult { @@ -321,7 +318,29 @@ impl ClickHouseDb { Self { client } } + async fn get_full_account_at( + &self, + pubkey: &Pubkey, + slot: u64, + tx_index_in_block: Option, + ) -> DbResult> { + if let Some(tx_index_in_block) = tx_index_in_block { + return if let Some(account) = self + .get_account_at_index_in_block(pubkey, slot, tx_index_in_block) + .await? + { + Ok(Some(account)) + } else { + self.get_account_at_slot(pubkey, slot - 1) + .await + .map_err(|e| anyhow!("Failed to get NEON_REVISION, error: {e}")) + }; + } + self.get_account_at_slot(pubkey, slot) + .await + .map_err(|e| anyhow!("Failed to get NEON_REVISION, error: {e}")) + } async fn get_branch_slots(&self, slot: Option) -> ChResult<(u64, Vec)> { fn branch_from( rows: Vec, @@ -653,6 +672,7 @@ impl ClickHouseDb { &self, pubkey: &Pubkey, sol_sig: &[u8; 64], + bin_slice: Option, ) -> DbResult> { let sol_sig_str = bs58::encode(sol_sig).into_string(); info!("get_account_by_sol_sig {{ pubkey: {pubkey}, sol_sig: {sol_sig_str} }}"); @@ -734,7 +754,7 @@ impl ClickHouseDb { // If not found, get closest account state in one of previous slots if let Some(parent) = slot.parent { - self.get_account_at(pubkey, parent, None).await + self.get_account_at(pubkey, parent, None, bin_slice).await } else { Ok(None) } diff --git a/evm_loader/lib/src/types/tracer_rocks_db.rs b/evm_loader/lib/src/types/tracer_rocks_db.rs index e4d5191d6..340f5636b 100644 --- a/evm_loader/lib/src/types/tracer_rocks_db.rs +++ b/evm_loader/lib/src/types/tracer_rocks_db.rs @@ -6,6 +6,7 @@ use jsonrpsee::core::Serialize; use jsonrpsee::rpc_params; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use serde_json::from_str; +use solana_account_decoder::UiDataSliceConfig; use solana_sdk::signature::Signature; use solana_sdk::{ account::Account, @@ -94,14 +95,15 @@ impl TracerDbTrait for RocksDb { pubkey: &Pubkey, slot: u64, tx_index_in_block: Option, + maybe_bin_slice: Option, ) -> DbResult> { - info!("get_account_at {pubkey:?}, slot: {slot:?}, tx_index: {tx_index_in_block:?}"); + info!("get_account_at {pubkey:?}, slot: {slot:?}, tx_index: {tx_index_in_block:?}, bin_slice: {maybe_bin_slice:?}"); let response: String = self .client .request( "get_account", - rpc_params![pubkey.to_string(), slot, tx_index_in_block], + rpc_params![pubkey.to_string(), slot, tx_index_in_block, maybe_bin_slice], ) .await?;