diff --git a/evm_loader/api/src/api_context.rs b/evm_loader/api/src/api_context.rs index 409638aff..1d040a2d1 100644 --- a/evm_loader/api/src/api_context.rs +++ b/evm_loader/api/src/api_context.rs @@ -6,19 +6,21 @@ use std::sync::Arc; pub async fn build_rpc_client( state: &NeonApiState, slot: Option, + tx_index_in_block: Option, ) -> Result, NeonError> { if let Some(slot) = slot { - return build_call_db_client(state, slot).await; + build_call_db_client(state, slot, tx_index_in_block).await + } else { + Ok(state.rpc_client.clone()) } - - Ok(state.rpc_client.clone()) } pub async fn build_call_db_client( state: &NeonApiState, slot: u64, + tx_index_in_block: Option, ) -> Result, NeonError> { Ok(Arc::new( - CallDbClient::new(state.tracer_db.clone(), slot).await?, + CallDbClient::new(state.tracer_db.clone(), slot, tx_index_in_block).await?, )) } diff --git a/evm_loader/api/src/api_server/handlers/emulate.rs b/evm_loader/api/src/api_server/handlers/emulate.rs index 1a3da87a7..f2d2119bd 100644 --- a/evm_loader/api/src/api_server/handlers/emulate.rs +++ b/evm_loader/api/src/api_server/handlers/emulate.rs @@ -19,7 +19,13 @@ pub async fn emulate( ) -> impl Responder { let tx = emulate_request.tx_params.into(); - let rpc_client = match api_context::build_rpc_client(&state, emulate_request.slot).await { + let rpc_client = match api_context::build_rpc_client( + &state, + emulate_request.slot, + emulate_request.tx_index_in_block, + ) + .await + { Ok(rpc_client) => rpc_client, Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), }; diff --git a/evm_loader/api/src/api_server/handlers/get_ether_account_data.rs b/evm_loader/api/src/api_server/handlers/get_ether_account_data.rs index e0e77e50d..bc4d9b70d 100644 --- a/evm_loader/api/src/api_server/handlers/get_ether_account_data.rs +++ b/evm_loader/api/src/api_server/handlers/get_ether_account_data.rs @@ -14,7 +14,7 @@ pub async fn get_ether_account_data( request_id: RequestId, Query(req_params): Query, ) -> impl Responder { - let rpc_client = match api_context::build_rpc_client(&state, req_params.slot).await { + let rpc_client = match api_context::build_rpc_client(&state, req_params.slot, None).await { Ok(rpc_client) => rpc_client, Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), }; diff --git a/evm_loader/api/src/api_server/handlers/get_storage_at.rs b/evm_loader/api/src/api_server/handlers/get_storage_at.rs index c19d0046a..7e3d36d2a 100644 --- a/evm_loader/api/src/api_server/handlers/get_storage_at.rs +++ b/evm_loader/api/src/api_server/handlers/get_storage_at.rs @@ -17,7 +17,7 @@ pub async fn get_storage_at( request_id: RequestId, Query(req_params): Query, ) -> impl Responder { - let rpc_client = match api_context::build_rpc_client(&state, req_params.slot).await { + let rpc_client = match api_context::build_rpc_client(&state, req_params.slot, None).await { Ok(rpc_client) => rpc_client, Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), }; diff --git a/evm_loader/api/src/api_server/handlers/trace.rs b/evm_loader/api/src/api_server/handlers/trace.rs index d53d552de..1a707b8b5 100644 --- a/evm_loader/api/src/api_server/handlers/trace.rs +++ b/evm_loader/api/src/api_server/handlers/trace.rs @@ -19,11 +19,16 @@ pub async fn trace( ) -> impl Responder { let tx = trace_request.emulate_request.tx_params.into(); - let rpc_client = - match api_context::build_rpc_client(&state, trace_request.emulate_request.slot).await { - Ok(rpc_client) => rpc_client, - Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), - }; + let rpc_client = match api_context::build_rpc_client( + &state, + trace_request.emulate_request.slot, + trace_request.emulate_request.tx_index_in_block, + ) + .await + { + Ok(rpc_client) => rpc_client, + Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), + }; let context = Context::new(&*rpc_client, &state.config); diff --git a/evm_loader/cli/src/main.rs b/evm_loader/cli/src/main.rs index 615ce4419..333ab0e73 100644 --- a/evm_loader/cli/src/main.rs +++ b/evm_loader/cli/src/main.rs @@ -59,6 +59,7 @@ async fn run<'a>(options: &'a ArgMatches<'a>) -> NeonCliResult { CallDbClient::new( TracerDb::new(config.db_config.as_ref().expect("db-config not found")), slot, + None, ) .await?, ) diff --git a/evm_loader/lib/src/rpc/db_call_client.rs b/evm_loader/lib/src/rpc/db_call_client.rs index 29a972f41..aa3e9ab74 100644 --- a/evm_loader/lib/src/rpc/db_call_client.rs +++ b/evm_loader/lib/src/rpc/db_call_client.rs @@ -24,11 +24,16 @@ use std::any::Any; pub struct CallDbClient { tracer_db: TracerDb, - pub slot: u64, + slot: u64, + tx_index_in_block: Option, } impl CallDbClient { - pub async fn new(tracer_db: TracerDb, slot: u64) -> Result { + pub async fn new( + tracer_db: TracerDb, + slot: u64, + tx_index_in_block: Option, + ) -> Result { let earliest_rooted_slot = tracer_db .get_earliest_rooted_slot() .await @@ -37,7 +42,11 @@ impl CallDbClient { return Err(NeonError::EarlySlot(slot, earliest_rooted_slot)); } - Ok(Self { tracer_db, slot }) + Ok(Self { + tracer_db, + slot, + tx_index_in_block, + }) } } @@ -60,7 +69,7 @@ impl Rpc for CallDbClient { async fn get_account(&self, key: &Pubkey) -> ClientResult { self.tracer_db - .get_account_at(key, self.slot) + .get_account_at(key, self.slot, self.tx_index_in_block) .await .map_err(|e| e!("load account error", key, e))? .ok_or_else(|| e!("account not found", key)) @@ -73,7 +82,7 @@ impl Rpc for CallDbClient { ) -> RpcResult> { let account = self .tracer_db - .get_account_at(key, self.slot) + .get_account_at(key, self.slot, self.tx_index_in_block) .await .map_err(|e| e!("load account error", key, e))?; @@ -95,7 +104,7 @@ impl Rpc for CallDbClient { for key in pubkeys { let account = self .tracer_db - .get_account_at(key, self.slot) + .get_account_at(key, self.slot, self.tx_index_in_block) .await .map_err(|e| e!("load account error", key, e))?; result.push(account); diff --git a/evm_loader/lib/src/types/request_models.rs b/evm_loader/lib/src/types/request_models.rs index a1f3b180e..54cdb2db9 100644 --- a/evm_loader/lib/src/types/request_models.rs +++ b/evm_loader/lib/src/types/request_models.rs @@ -102,6 +102,7 @@ pub struct EmulateRequestModel { #[serde(flatten)] pub emulation_params: EmulationParamsRequestModel, pub slot: Option, + pub tx_index_in_block: Option, } #[derive(Deserialize, Serialize, Debug, Default)] diff --git a/evm_loader/lib/src/types/tracer_ch_db.rs b/evm_loader/lib/src/types/tracer_ch_db.rs index f60cf9c2b..81851ac7f 100644 --- a/evm_loader/lib/src/types/tracer_ch_db.rs +++ b/evm_loader/lib/src/types/tracer_ch_db.rs @@ -9,7 +9,7 @@ use super::{ }; use clickhouse::Client; -use log::{debug, info}; +use log::{debug, error, info}; use rand::Rng; use solana_sdk::{ account::Account, @@ -220,11 +220,32 @@ impl ClickHouseDb { Ok(slot_opt) } - #[allow(clippy::too_many_lines)] - pub async fn get_account_at(&self, pubkey: &Pubkey, slot: u64) -> ChResult> { - info!("get_account_at {{ pubkey: {pubkey}, slot: {slot} }}"); + pub async fn get_account_at( + &self, + pubkey: &Pubkey, + slot: u64, + tx_index_in_block: Option, + ) -> ChResult> { + if let Some(tx_index_in_block) = tx_index_in_block { + if let Some(account) = self + .get_account_at_index_in_block(pubkey, slot, tx_index_in_block) + .await? + { + return Ok(Some(account)); + } + } + + self.get_account_at_slot(pubkey, slot).await + } + + async fn get_account_at_slot( + &self, + pubkey: &Pubkey, + slot: u64, + ) -> Result, ChError> { + info!("get_account_at_slot {{ pubkey: {pubkey}, slot: {slot} }}"); let (first, mut branch) = self.get_branch_slots(Some(slot)).await.map_err(|e| { - println!("get_branch_slots error: {:?}", e); + error!("get_branch_slots error: {:?}", e); e })?; @@ -234,7 +255,7 @@ impl ClickHouseDb { .get_account_rooted_slot(&pubkey_str, first) .await .map_err(|e| { - println!("get_account_rooted_slot error: {:?}", e); + error!("get_account_rooted_slot error: {:?}", e); e })? { @@ -263,12 +284,12 @@ impl ClickHouseDb { .await, ) .map_err(|e| { - println!("get_account_at error: {e}"); + error!("get_account_at_slot error: {e}"); ChError::Db(e) })?; let execution_time = Instant::now().duration_since(time_start); info!( - "get_account_at {{ pubkey: {pubkey}, slot: {slot} }} sql(1) returned {row:?}, time: {} sec", + "get_account_at_slot {{ pubkey: {pubkey}, slot: {slot} }} sql(1) returned {row:?}, time: {} sec", execution_time.as_secs_f64() ); @@ -285,19 +306,64 @@ impl ClickHouseDb { ); } - let result = if let Some(acc) = row { - acc.try_into() - .map(Some) - .map_err(|err| ChError::Db(clickhouse::error::Error::Custom(err))) - } else { - Ok(None) - }; + let result = row + .map(|a| a.try_into()) + .transpose() + .map_err(|e| ChError::Db(clickhouse::error::Error::Custom(e))); - info!("get_account_at {{ pubkey: {pubkey}, slot: {slot} }} -> {result:?}"); + info!("get_account_at_slot {{ pubkey: {pubkey}, slot: {slot} }} -> {result:?}"); result } + async fn get_account_at_index_in_block( + &self, + pubkey: &Pubkey, + slot: u64, + tx_index_in_block: u64, + ) -> ChResult> { + info!( + "get_account_at_index_in_block {{ pubkey: {pubkey}, slot: {slot}, tx_index_in_block: {tx_index_in_block} }}" + ); + + let query = r#" + SELECT owner, lamports, executable, rent_epoch, data, txn_signature + FROM events.update_account_distributed + WHERE pubkey = ? + AND slot = ? + AND write_version <= ? + ORDER BY write_version DESC + LIMIT 1 + "#; + + let time_start = Instant::now(); + + let account = Self::row_opt( + self.client + .query(query) + .bind(format!("{:?}", pubkey.to_bytes())) + .bind(slot) + .bind(tx_index_in_block) + .fetch_one::() + .await, + ) + .map_err(|e| { + error!("get_account_at_index_in_block error: {e}"); + ChError::Db(e) + })? + .map(|a| a.try_into()) + .transpose() + .map_err(|e| ChError::Db(clickhouse::error::Error::Custom(e)))?; + + let execution_time = Instant::now().duration_since(time_start); + info!( + "get_account_at_index_in_block {{ pubkey: {pubkey}, slot: {slot}, tx_index_in_block: {tx_index_in_block} }} sql(1) returned {account:?}, time: {} sec", + execution_time.as_secs_f64() + ); + + Ok(account) + } + async fn get_older_account_row_at( &self, pubkey: &str, @@ -470,7 +536,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).await + self.get_account_at(pubkey, parent, None).await } else { Ok(None) }