Skip to content

Commit

Permalink
NDEV-3112. Optimize getting of deactivated features (#566)
Browse files Browse the repository at this point in the history
* NDEV-3112. Optimize getting of deactivated features

* NDEV-3112. Fix incorrect logic

* NDEV-3112. Make unique cache for deactivated features

* NDEV-3112. Fix bug, add test for deactivated_features

---------

Co-authored-by: Dzmitry Zdanovich <[email protected]>
Co-authored-by: Semen Medvedev <[email protected]>
  • Loading branch information
3 people authored Jan 21, 2025
1 parent 9c9a415 commit d55ea02
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 73 deletions.
1 change: 1 addition & 0 deletions evm_loader/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions evm_loader/api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use neon_lib::commands;
pub use neon_lib::config;
pub use neon_lib::errors;
pub use neon_lib::types;
pub use neon_lib::types::deactivated_features::set_deactivated_features_rpc;
use tracing_appender::non_blocking::NonBlockingBuilder;

use actix_request_identifier::RequestIdentifier;
Expand Down Expand Up @@ -58,6 +59,8 @@ async fn main() -> NeonApiResult<()> {
let api_config = config::load_api_config_from_environment();
let state: NeonApiState = Data::new(State::new(api_config).await);

set_deactivated_features_rpc(state.rpc_client.clone()).await;

let listener_addr = options
.value_of("host")
.map(std::borrow::ToOwned::to_owned)
Expand Down
33 changes: 11 additions & 22 deletions evm_loader/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use neon_lib::{
get_storage_at, init_environment, trace,
},
rpc::CloneRpcClient,
types::{BalanceAddress, EmulateRequest},
types::{deactivated_features::set_deactivated_features_rpc, BalanceAddress, EmulateRequest},
Config,
};

Expand Down Expand Up @@ -42,10 +42,15 @@ type NeonCliResult = Result<serde_json::Value, NeonError>;
async fn run(options: &ArgMatches<'_>) -> NeonCliResult {
let config = &config::create(options)?;

let rpc = build_rpc(options, config).await?;

set_deactivated_features_rpc(RpcEnum::CloneRpcClient(CloneRpcClient::new_from_config(
config,
)))
.await;

match options.subcommand() {
("emulate", Some(_)) => {
let rpc = build_rpc(options, config).await?;

let request = read_tx_from_stdin()?;
emulate::execute(
&rpc,
Expand All @@ -58,16 +63,12 @@ async fn run(options: &ArgMatches<'_>) -> NeonCliResult {
.map(|(result, _)| json!(result))
}
("trace", Some(_)) => {
let rpc = build_rpc(options, config).await?;

let request = read_tx_from_stdin()?;
trace::trace_transaction(&rpc, &config.db_config, &config.evm_loader, request)
.await
.map(|trace| json!(trace))
}
("get-ether-account-data", Some(params)) => {
let rpc = build_rpc(options, config).await?;

let address = address_of(params, "ether").unwrap();
let chain_id = value_of(params, "chain_id").unwrap();

Expand All @@ -79,8 +80,6 @@ async fn run(options: &ArgMatches<'_>) -> NeonCliResult {
.map(|result| json!(result))
}
("get-contract-account-data", Some(params)) => {
let rpc = build_rpc(options, config).await?;

let account = address_of(params, "address").unwrap();
let accounts = std::slice::from_ref(&account);

Expand All @@ -89,17 +88,13 @@ async fn run(options: &ArgMatches<'_>) -> NeonCliResult {
.map(|result| json!(result))
}
("get-holder-account-data", Some(params)) => {
let rpc = build_rpc(options, config).await?;

let account = pubkey_of(params, "account").unwrap();

get_holder::execute(&rpc, &config.evm_loader, account)
.await
.map(|result| json!(result))
}
("neon-elf-params", Some(params)) => {
let rpc = build_rpc(options, config).await?;

let program_location = params.value_of("program_location");
get_neon_elf::execute(config, &rpc, program_location)
.await
Expand Down Expand Up @@ -135,22 +130,16 @@ async fn run(options: &ArgMatches<'_>) -> NeonCliResult {
.map(|result| json!(result))
}
("get-storage-at", Some(params)) => {
let rpc = build_rpc(options, config).await?;

let contract_id = address_of(params, "contract_id").expect("contract_it parse error");
let index = u256_of(params, "index").expect("index parse error");

get_storage_at::execute(&rpc, &config.evm_loader, contract_id, index)
.await
.map(|hash| json!(hex::encode(hash.0)))
}
("config", Some(_)) => {
let rpc = build_rpc(options, config).await?;

get_config::execute(&rpc, config.evm_loader)
.await
.map(|result| json!(result))
}
("config", Some(_)) => get_config::execute(&rpc, config.evm_loader)
.await
.map(|result| json!(result)),
_ => unreachable!(),
}
}
Expand Down
1 change: 1 addition & 0 deletions evm_loader/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ lazy_static = "1.5.0"
elsa = "1.10.0"
arrayref = "0.3.8"
futures = "0.3.30"
once_cell = "1.19.0"

[dev-dependencies]
hex-literal = "0.4.1"
Expand Down
130 changes: 130 additions & 0 deletions evm_loader/lib/src/deactivated_features_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
mod deactivated_features_tests {
use std::collections::HashMap;

use async_trait::async_trait;
use solana_account_decoder::UiDataSliceConfig as SliceConfig;
use solana_client::client_error::Result as ClientResult;
use solana_sdk::{
account::{Account, AccountSharedData},
clock::{Slot, UnixTimestamp},
feature::Feature,
feature_set,
pubkey::Pubkey,
};

use crate::{
rpc::Rpc,
types::deactivated_features::{
get_deactivated_features_at_slot, set_deactivated_features_rpc,
},
};

#[derive(Clone)]
struct RpcMockFeatures {
pub features: HashMap<Pubkey, Option<u64>>,
}

impl Default for RpcMockFeatures {
fn default() -> Self {
let accounts: Vec<_> = feature_set::FEATURE_NAMES.keys().copied().collect();

let mut features = HashMap::<Pubkey, Option<u64>>::new();

for (slot, account) in accounts.iter().enumerate() {
let slot = if slot == 0 { None } else { Some(slot as u64) };
features.insert(*account, slot);
}

Self { features }
}
}

#[async_trait(?Send)]
impl Rpc for RpcMockFeatures {
async fn get_account_slice(
&self,
_key: &Pubkey,
_slice: Option<SliceConfig>,
) -> ClientResult<Option<Account>> {
todo!()
}

async fn get_multiple_accounts(
&self,
pubkeys: &[Pubkey],
) -> ClientResult<Vec<Option<Account>>> {
let mut result: Vec<Option<Account>> = vec![];

for pubkey in pubkeys.iter() {
let feature = self.features.get(pubkey);

match feature {
Some(slot) => {
let mut data =
AccountSharedData::new(1_000_000_000, 100, &solana_sdk::feature::ID);
if solana_sdk::feature::to_account(
&Feature {
activated_at: *slot,
},
&mut data,
)
.is_some()
{
result.push(Some(data.into()));
} else {
result.push(None)
}
}
None => result.push(None),
}
}

Ok(result)
}

async fn get_block_time(&self, _slot: Slot) -> ClientResult<UnixTimestamp> {
todo!()
}

async fn get_slot(&self) -> ClientResult<Slot> {
todo!()
}

async fn get_deactivated_solana_features(&self) -> ClientResult<Vec<Pubkey>> {
todo!()
}
}

async fn get_deactivated_features_and_wait(slot: Option<u64>) -> Vec<Pubkey> {
get_deactivated_features_at_slot(slot)
.await
.expect("get deactivated features failed at slot unexpectedly")
}

// by default? mock rpc contains vector of features: RpcMockFeatures::features
// features[0] is not activated
// features[1] is activated at slot 1
// features[i] is activated at slot i
// let we have N features overall, then...
// only 1 feature will be deactivated on slot N (features[0])
// only 2 features will be deactivated on slot N-1 (features[0], features[n-1] (last one) which should be activated at slot N)
// ...
// all features will be deactivated on slot 0
#[tokio::test]
async fn test_deactivated_features_cache() {
let rpc = RpcMockFeatures::default();
let features_cnt = rpc.features.len() as u64;
set_deactivated_features_rpc(rpc.clone()).await;

for i in 0..features_cnt {
let features = get_deactivated_features_and_wait(Some(i)).await;

assert_eq!(features_cnt - i, features.len() as u64);
}
{
// current slot
let features = get_deactivated_features_and_wait(None).await;
assert_eq!(features.len(), 1);
}
}
}
3 changes: 2 additions & 1 deletion evm_loader/lib/src/rpc/db_call_client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{e, Rpc, SliceConfig};
use crate::types::deactivated_features::get_deactivated_features_at_slot;
use crate::types::{TracerDb, TracerDbTrait};
use crate::NeonError;
use crate::NeonError::RocksDb;
Expand Down Expand Up @@ -73,6 +74,6 @@ impl Rpc for CallDbClient {
}

async fn get_deactivated_solana_features(&self) -> ClientResult<Vec<Pubkey>> {
Ok(vec![]) // TODO
get_deactivated_features_at_slot(Some(self.slot)).await
}
}
52 changes: 4 additions & 48 deletions evm_loader/lib/src/rpc/validator_client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{config::APIOptions, Config};
use crate::{
config::APIOptions, types::deactivated_features::get_deactivated_features_at_slot, Config,
};

use super::{Rpc, SliceConfig};
use async_trait::async_trait;
Expand Down Expand Up @@ -167,52 +169,6 @@ impl Rpc for CloneRpcClient {
}

async fn get_deactivated_solana_features(&self) -> ClientResult<Vec<Pubkey>> {
use std::time::{Duration, Instant};
use tokio::sync::Mutex;

struct Cache {
data: Vec<Pubkey>,
timestamp: Instant,
}

static CACHE: Mutex<Option<Cache>> = Mutex::const_new(None);
let mut cache = CACHE.lock().await;

if let Some(cache) = cache.as_ref() {
if cache.timestamp.elapsed() < Duration::from_secs(24 * 60 * 60) {
return Ok(cache.data.clone());
}
}

let feature_keys: Vec<Pubkey> = solana_sdk::feature_set::FEATURE_NAMES
.keys()
.copied()
.collect();

let features = Rpc::get_multiple_accounts(self, &feature_keys).await?;

let mut result = Vec::with_capacity(feature_keys.len());
for (pubkey, feature) in feature_keys.iter().zip(features) {
let is_activated = feature
.and_then(|a| solana_sdk::feature::from_account(&a))
.and_then(|f| f.activated_at)
.is_some();

if !is_activated {
result.push(*pubkey);
}
}

for feature in &result {
debug!("Deactivated feature: {}", feature);
}

cache.replace(Cache {
data: result.clone(),
timestamp: Instant::now(),
});
drop(cache);

Ok(result)
get_deactivated_features_at_slot(None).await
}
}
Loading

0 comments on commit d55ea02

Please sign in to comment.