1use std::{
2 cmp::max,
3 collections::{BTreeMap, HashMap, HashSet, VecDeque},
4 str::FromStr,
5 time::SystemTime,
6};
7
8use agave_feature_set::{FeatureSet, enable_extend_program_checked};
9use base64::{Engine, prelude::BASE64_STANDARD};
10use chrono::Utc;
11use convert_case::Casing;
12use crossbeam_channel::{Receiver, Sender, unbounded};
13use litesvm::types::{
14 FailedTransactionMetadata, SimulatedTransactionInfo, TransactionMetadata, TransactionResult,
15};
16use solana_account::{Account, AccountSharedData, ReadableAccount};
17use solana_account_decoder::{
18 UiAccount, UiAccountData, UiAccountEncoding, UiDataSliceConfig, encode_ui_account,
19 parse_account_data::{AccountAdditionalDataV3, ParsedAccount, SplTokenAdditionalDataV2},
20};
21use solana_client::{
22 rpc_client::SerializableTransaction,
23 rpc_config::{RpcAccountInfoConfig, RpcBlockConfig, RpcTransactionLogsFilter},
24 rpc_filter::RpcFilterType,
25 rpc_response::{RpcKeyedAccount, RpcLogsResponse, RpcPerfSample},
26};
27use solana_clock::{Clock, Slot};
28use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
29use solana_epoch_info::EpochInfo;
30use solana_epoch_schedule::EpochSchedule;
31use solana_feature_gate_interface::Feature;
32use solana_genesis_config::GenesisConfig;
33use solana_hash::Hash;
34use solana_inflation::Inflation;
35use solana_loader_v3_interface::state::UpgradeableLoaderState;
36use solana_message::{
37 Message, VersionedMessage, inline_nonce::is_advance_nonce_instruction_data, v0::LoadedAddresses,
38};
39use solana_program_option::COption;
40use solana_pubkey::Pubkey;
41use solana_rpc_client_api::response::SlotInfo;
42use solana_sdk_ids::{bpf_loader, system_program};
43use solana_signature::Signature;
44use solana_system_interface::instruction as system_instruction;
45use solana_transaction::versioned::VersionedTransaction;
46use solana_transaction_error::TransactionError;
47use solana_transaction_status::{TransactionDetails, TransactionStatusMeta, UiConfirmedBlock};
48use spl_token_2022_interface::extension::{
49 BaseStateWithExtensions, StateWithExtensions, interest_bearing_mint::InterestBearingConfig,
50 scaled_ui_amount::ScaledUiAmountConfig,
51};
52use surfpool_types::{
53 AccountChange, AccountProfileState, AccountSnapshot, DEFAULT_PROFILING_MAP_CAPACITY,
54 DEFAULT_SLOT_TIME_MS, ExportSnapshotConfig, ExportSnapshotScope, FifoMap, Idl,
55 OverrideInstance, ProfileResult, RpcProfileDepth, RpcProfileResultConfig,
56 RunbookExecutionStatusReport, SimnetEvent, SvmFeatureConfig, TransactionConfirmationStatus,
57 TransactionStatusEvent, UiAccountChange, UiAccountProfileState, UiProfileResult, VersionedIdl,
58 types::{
59 ComputeUnitsEstimationResult, KeyedProfileResult, UiKeyedProfileResult, UuidOrSignature,
60 },
61};
62use txtx_addon_kit::{
63 indexmap::IndexMap,
64 types::types::{AddonJsonConverter, Value},
65};
66use txtx_addon_network_svm::codec::idl::borsh_encode_value_to_idl_type;
67use txtx_addon_network_svm_types::subgraph::idl::{
68 parse_bytes_to_value_with_expected_idl_type_def_ty,
69 parse_bytes_to_value_with_expected_idl_type_def_ty_with_leftover_bytes,
70};
71use uuid::Uuid;
72
73use super::{
74 AccountSubscriptionData, BlockHeader, BlockIdentifier, FINALIZATION_SLOT_THRESHOLD,
75 GetAccountResult, GeyserBlockMetadata, GeyserEntryInfo, GeyserEvent, GeyserSlotStatus,
76 ProgramSubscriptionData, SLOTS_PER_EPOCH, SignatureSubscriptionData, SignatureSubscriptionType,
77 remote::SurfnetRemoteClient,
78};
79use crate::{
80 error::{SurfpoolError, SurfpoolResult},
81 rpc::utils::convert_transaction_metadata_from_canonical,
82 scenarios::TemplateRegistry,
83 storage::{OverlayStorage, Storage, new_kv_store, new_kv_store_with_default},
84 surfnet::{
85 LogsSubscriptionData, locker::is_supported_token_program, surfnet_lite_svm::SurfnetLiteSvm,
86 },
87 types::{
88 GeyserAccountUpdate, MintAccount, SerializableAccountAdditionalData,
89 SurfnetTransactionStatus, SyntheticBlockhash, TokenAccount, TransactionWithStatusMeta,
90 },
91};
92
93lazy_static::lazy_static! {
94 pub static ref GARBAGE_COLLECTION_INTERVAL_SLOTS: u64 = {
98 std::env::var("SURFPOOL_GARBAGE_COLLECTION_INTERVAL_SLOTS")
99 .ok()
100 .and_then(|s| s.parse().ok())
101 .unwrap_or(9_000)
102 };
103
104 pub static ref CHECKPOINT_INTERVAL_SLOTS: u64 = {
108 std::env::var("SURFPOOL_CHECKPOINT_INTERVAL_SLOTS")
109 .ok()
110 .and_then(|s| s.parse().ok())
111 .unwrap_or(150)
112 };
113}
114
115pub fn apply_override_to_decoded_account(
117 decoded_value: &mut Value,
118 path: &str,
119 value: &serde_json::Value,
120) -> SurfpoolResult<()> {
121 let parts: Vec<&str> = path.split('.').collect();
122
123 if parts.is_empty() {
124 return Err(SurfpoolError::internal("Empty path provided for override"));
125 }
126
127 let mut current = decoded_value;
129 for part in &parts[..parts.len() - 1] {
130 match current {
131 Value::Object(map) => {
132 current = map.get_mut(&part.to_string()).ok_or_else(|| {
133 SurfpoolError::internal(format!(
134 "Path segment '{}' not found in decoded account",
135 part
136 ))
137 })?;
138 }
139 _ => {
140 return Err(SurfpoolError::internal(format!(
141 "Cannot navigate through field '{}' - not an object",
142 part
143 )));
144 }
145 }
146 }
147
148 let final_key = parts[parts.len() - 1];
150 match current {
151 Value::Object(map) => {
152 let txtx_value = json_to_txtx_value(value)?;
154 map.insert(final_key.to_string(), txtx_value);
155 Ok(())
156 }
157 _ => Err(SurfpoolError::internal(format!(
158 "Cannot set field '{}' - parent is not an object",
159 final_key
160 ))),
161 }
162}
163
164fn json_to_txtx_value(json: &serde_json::Value) -> SurfpoolResult<Value> {
166 match json {
167 serde_json::Value::Null => Ok(Value::Null),
168 serde_json::Value::Bool(b) => Ok(Value::Bool(*b)),
169 serde_json::Value::Number(n) => {
170 if let Some(i) = n.as_i64() {
171 Ok(Value::Integer(i as i128))
172 } else if let Some(u) = n.as_u64() {
173 Ok(Value::Integer(u as i128))
174 } else if let Some(f) = n.as_f64() {
175 Ok(Value::Float(f))
176 } else {
177 Err(SurfpoolError::internal(format!(
178 "Unable to convert number: {}",
179 n
180 )))
181 }
182 }
183 serde_json::Value::String(s) => Ok(Value::String(s.clone())),
184 serde_json::Value::Array(arr) => {
185 let txtx_arr: Result<Vec<Value>, _> = arr.iter().map(json_to_txtx_value).collect();
186 Ok(Value::Array(Box::new(txtx_arr?)))
187 }
188 serde_json::Value::Object(obj) => {
189 let mut txtx_obj = IndexMap::new();
190 for (k, v) in obj.iter() {
191 txtx_obj.insert(k.clone(), json_to_txtx_value(v)?);
192 }
193 Ok(Value::Object(txtx_obj))
194 }
195 }
196}
197
198pub type AccountOwner = Pubkey;
199
200#[allow(deprecated)]
201use solana_sysvar::recent_blockhashes::MAX_ENTRIES;
202
203#[allow(deprecated)]
204pub const MAX_RECENT_BLOCKHASHES_STANDARD: usize = MAX_ENTRIES;
205
206pub fn get_txtx_value_json_converters() -> Vec<AddonJsonConverter<'static>> {
207 vec![
208 Box::new(move |value: &txtx_addon_kit::types::types::Value| {
209 txtx_addon_network_svm_types::SvmValue::to_json(value)
210 }) as AddonJsonConverter<'static>,
211 ]
212}
213
214#[derive(Clone)]
221pub struct SurfnetSvm {
222 pub inner: SurfnetLiteSvm,
223 pub remote_rpc_url: Option<String>,
224 pub chain_tip: BlockIdentifier,
225 pub blocks: Box<dyn Storage<u64, BlockHeader>>,
226 pub transactions: Box<dyn Storage<String, SurfnetTransactionStatus>>,
227 pub transactions_queued_for_confirmation: VecDeque<(
228 VersionedTransaction,
229 Sender<TransactionStatusEvent>,
230 Option<TransactionError>,
231 )>,
232 pub transactions_queued_for_finalization: VecDeque<(
233 Slot,
234 VersionedTransaction,
235 Sender<TransactionStatusEvent>,
236 Option<TransactionError>,
237 )>,
238 pub perf_samples: VecDeque<RpcPerfSample>,
239 pub transactions_processed: u64,
240 pub latest_epoch_info: EpochInfo,
241 pub simnet_events_tx: Sender<SimnetEvent>,
242 pub geyser_events_tx: Sender<GeyserEvent>,
243 pub signature_subscriptions: HashMap<Signature, Vec<SignatureSubscriptionData>>,
244 pub account_subscriptions: AccountSubscriptionData,
245 pub program_subscriptions: ProgramSubscriptionData,
246 pub slot_subscriptions: Vec<Sender<SlotInfo>>,
247 pub profile_tag_map: Box<dyn Storage<String, Vec<UuidOrSignature>>>,
248 pub simulated_transaction_profiles: Box<dyn Storage<String, KeyedProfileResult>>,
249 pub executed_transaction_profiles: Box<dyn Storage<String, KeyedProfileResult>>,
250 pub logs_subscriptions: Vec<LogsSubscriptionData>,
251 pub snapshot_subscriptions: Vec<super::SnapshotSubscriptionData>,
252 pub updated_at: u64,
253 pub slot_time: u64,
254 pub start_time: SystemTime,
255 pub accounts_by_owner: Box<dyn Storage<String, Vec<String>>>,
256 pub account_associated_data: Box<dyn Storage<String, SerializableAccountAdditionalData>>,
257 pub token_accounts: Box<dyn Storage<String, TokenAccount>>,
258 pub token_mints: Box<dyn Storage<String, MintAccount>>,
259 pub token_accounts_by_owner: Box<dyn Storage<String, Vec<String>>>,
260 pub token_accounts_by_delegate: Box<dyn Storage<String, Vec<String>>>,
261 pub token_accounts_by_mint: Box<dyn Storage<String, Vec<String>>>,
262 pub total_supply: u64,
263 pub circulating_supply: u64,
264 pub non_circulating_supply: u64,
265 pub non_circulating_accounts: Vec<String>,
266 pub genesis_config: GenesisConfig,
267 pub inflation: Inflation,
268 pub write_version: u64,
272 pub registered_idls: Box<dyn Storage<String, Vec<VersionedIdl>>>,
273 pub feature_set: FeatureSet,
274 pub instruction_profiling_enabled: bool,
275 pub max_profiles: usize,
276 pub runbook_executions: Vec<RunbookExecutionStatusReport>,
277 pub account_update_slots: HashMap<Pubkey, Slot>,
278 pub streamed_accounts: Box<dyn Storage<String, bool>>,
279 pub recent_blockhashes: VecDeque<(SyntheticBlockhash, i64)>,
280 pub scheduled_overrides: Box<dyn Storage<u64, Vec<OverrideInstance>>>,
281 pub closed_accounts: HashSet<Pubkey>,
284 pub genesis_slot: Slot,
287 pub genesis_updated_at: u64,
290 pub slot_checkpoint: Box<dyn Storage<String, u64>>,
293 pub last_checkpoint_slot: u64,
295}
296
297pub const FEATURE: Feature = Feature {
298 activated_at: Some(0),
299};
300
301impl SurfnetSvm {
302 pub fn default() -> (Self, Receiver<SimnetEvent>, Receiver<GeyserEvent>) {
303 Self::new(None, "0").unwrap()
304 }
305
306 pub fn new_with_db(
307 database_url: Option<&str>,
308 surfnet_id: &str,
309 ) -> SurfpoolResult<(Self, Receiver<SimnetEvent>, Receiver<GeyserEvent>)> {
310 Self::new(database_url, surfnet_id)
311 }
312
313 pub fn shutdown(&self) {
316 self.inner.shutdown();
317 self.blocks.shutdown();
318 self.transactions.shutdown();
319 self.token_accounts.shutdown();
320 self.token_mints.shutdown();
321 self.accounts_by_owner.shutdown();
322 self.token_accounts_by_owner.shutdown();
323 self.token_accounts_by_delegate.shutdown();
324 self.token_accounts_by_mint.shutdown();
325 self.streamed_accounts.shutdown();
326 self.scheduled_overrides.shutdown();
327 self.registered_idls.shutdown();
328 self.profile_tag_map.shutdown();
329 self.simulated_transaction_profiles.shutdown();
330 self.executed_transaction_profiles.shutdown();
331 self.account_associated_data.shutdown();
332 }
333
334 pub fn clone_for_profiling(&self) -> Self {
338 let (dummy_simnet_tx, _) = crossbeam_channel::bounded(1);
339 let (dummy_geyser_tx, _) = crossbeam_channel::bounded(1);
340
341 Self {
342 inner: self.inner.clone_for_profiling(),
343 remote_rpc_url: self.remote_rpc_url.clone(),
344 chain_tip: self.chain_tip.clone(),
345
346 blocks: OverlayStorage::wrap(self.blocks.clone_box()),
348 transactions: OverlayStorage::wrap(self.transactions.clone_box()),
349 profile_tag_map: OverlayStorage::wrap(self.profile_tag_map.clone_box()),
350 simulated_transaction_profiles: OverlayStorage::wrap(
351 self.simulated_transaction_profiles.clone_box(),
352 ),
353 executed_transaction_profiles: OverlayStorage::wrap(
354 self.executed_transaction_profiles.clone_box(),
355 ),
356 accounts_by_owner: OverlayStorage::wrap(self.accounts_by_owner.clone_box()),
357 account_associated_data: OverlayStorage::wrap(self.account_associated_data.clone_box()),
358 token_accounts: OverlayStorage::wrap(self.token_accounts.clone_box()),
359 token_mints: OverlayStorage::wrap(self.token_mints.clone_box()),
360 token_accounts_by_owner: OverlayStorage::wrap(self.token_accounts_by_owner.clone_box()),
361 token_accounts_by_delegate: OverlayStorage::wrap(
362 self.token_accounts_by_delegate.clone_box(),
363 ),
364 token_accounts_by_mint: OverlayStorage::wrap(self.token_accounts_by_mint.clone_box()),
365 registered_idls: OverlayStorage::wrap(self.registered_idls.clone_box()),
366 streamed_accounts: OverlayStorage::wrap(self.streamed_accounts.clone_box()),
367 scheduled_overrides: OverlayStorage::wrap(self.scheduled_overrides.clone_box()),
368
369 transactions_queued_for_confirmation: self.transactions_queued_for_confirmation.clone(),
371 transactions_queued_for_finalization: self.transactions_queued_for_finalization.clone(),
372 perf_samples: self.perf_samples.clone(),
373 transactions_processed: self.transactions_processed,
374 latest_epoch_info: self.latest_epoch_info.clone(),
375
376 simnet_events_tx: dummy_simnet_tx,
378 geyser_events_tx: dummy_geyser_tx,
379
380 signature_subscriptions: self.signature_subscriptions.clone(),
381 account_subscriptions: self.account_subscriptions.clone(),
382 program_subscriptions: self.program_subscriptions.clone(),
383 slot_subscriptions: Vec::new(),
385 logs_subscriptions: Vec::new(),
386 snapshot_subscriptions: Vec::new(),
387
388 updated_at: self.updated_at,
389 slot_time: self.slot_time,
390 start_time: self.start_time,
391
392 total_supply: self.total_supply,
393 circulating_supply: self.circulating_supply,
394 non_circulating_supply: self.non_circulating_supply,
395 non_circulating_accounts: self.non_circulating_accounts.clone(),
396 genesis_config: self.genesis_config.clone(),
397 inflation: self.inflation,
398 write_version: self.write_version,
399 feature_set: self.feature_set.clone(),
400 instruction_profiling_enabled: self.instruction_profiling_enabled,
401 max_profiles: self.max_profiles,
402 runbook_executions: self.runbook_executions.clone(),
403 account_update_slots: self.account_update_slots.clone(),
404 recent_blockhashes: self.recent_blockhashes.clone(),
405 closed_accounts: self.closed_accounts.clone(),
406 genesis_slot: self.genesis_slot,
407 genesis_updated_at: self.genesis_updated_at,
408 slot_checkpoint: OverlayStorage::wrap(self.slot_checkpoint.clone_box()),
409 last_checkpoint_slot: self.last_checkpoint_slot,
410 }
411 }
412
413 pub fn new(
417 database_url: Option<&str>,
418 surfnet_id: &str,
419 ) -> SurfpoolResult<(Self, Receiver<SimnetEvent>, Receiver<GeyserEvent>)> {
420 let (simnet_events_tx, simnet_events_rx) = crossbeam_channel::bounded(1024);
421 let (geyser_events_tx, geyser_events_rx) = crossbeam_channel::bounded(1024);
422
423 let mut feature_set = FeatureSet::all_enabled();
424
425 feature_set.deactivate(&enable_extend_program_checked::id());
428
429 let inner =
430 SurfnetLiteSvm::new().initialize(feature_set.clone(), database_url, surfnet_id)?;
431
432 let native_mint_account = inner
433 .get_account(&spl_token_interface::native_mint::ID)?
434 .unwrap();
435
436 let native_mint_associated_data = {
437 let mint = StateWithExtensions::<spl_token_2022_interface::state::Mint>::unpack(
438 &native_mint_account.data,
439 )
440 .unwrap();
441 let unix_timestamp = inner.get_sysvar::<Clock>().unix_timestamp;
442 let interest_bearing_config = mint
443 .get_extension::<InterestBearingConfig>()
444 .map(|x| (*x, unix_timestamp))
445 .ok();
446 let scaled_ui_amount_config = mint
447 .get_extension::<ScaledUiAmountConfig>()
448 .map(|x| (*x, unix_timestamp))
449 .ok();
450 AccountAdditionalDataV3 {
451 spl_token_additional_data: Some(SplTokenAdditionalDataV2 {
452 decimals: mint.base.decimals,
453 interest_bearing_config,
454 scaled_ui_amount_config,
455 }),
456 }
457 };
458 let parsed_mint_account = MintAccount::unpack(&native_mint_account.data).unwrap();
459
460 let mut accounts_by_owner_db: Box<dyn Storage<String, Vec<String>>> =
462 new_kv_store(&database_url, "accounts_by_owner", surfnet_id)?;
463 accounts_by_owner_db.store(
464 native_mint_account.owner.to_string(),
465 vec![spl_token_interface::native_mint::ID.to_string()],
466 )?;
467 let blocks_db = new_kv_store(&database_url, "blocks", surfnet_id)?;
468 let transactions_db = new_kv_store(&database_url, "transactions", surfnet_id)?;
469 let token_accounts_db = new_kv_store(&database_url, "token_accounts", surfnet_id)?;
470 let mut token_mints_db: Box<dyn Storage<String, MintAccount>> =
471 new_kv_store(&database_url, "token_mints", surfnet_id)?;
472 let mut account_associated_data_db: Box<
473 dyn Storage<String, SerializableAccountAdditionalData>,
474 > = new_kv_store(&database_url, "account_associated_data", surfnet_id)?;
475 account_associated_data_db.store(
477 spl_token_interface::native_mint::ID.to_string(),
478 native_mint_associated_data.into(),
479 )?;
480 token_mints_db.store(
481 spl_token_interface::native_mint::ID.to_string(),
482 parsed_mint_account,
483 )?;
484 let token_accounts_by_owner_db: Box<dyn Storage<String, Vec<String>>> =
485 new_kv_store(&database_url, "token_accounts_by_owner", surfnet_id)?;
486 let token_accounts_by_delegate_db: Box<dyn Storage<String, Vec<String>>> =
487 new_kv_store(&database_url, "token_accounts_by_delegate", surfnet_id)?;
488 let token_accounts_by_mint_db: Box<dyn Storage<String, Vec<String>>> =
489 new_kv_store(&database_url, "token_accounts_by_mint", surfnet_id)?;
490 let streamed_accounts_db: Box<dyn Storage<String, bool>> =
491 new_kv_store(&database_url, "streamed_accounts", surfnet_id)?;
492 let scheduled_overrides_db: Box<dyn Storage<u64, Vec<OverrideInstance>>> =
493 new_kv_store(&database_url, "scheduled_overrides", surfnet_id)?;
494 let registered_idls_db: Box<dyn Storage<String, Vec<VersionedIdl>>> =
495 new_kv_store(&database_url, "registered_idls", surfnet_id)?;
496 let profile_tag_map_db: Box<dyn Storage<String, Vec<UuidOrSignature>>> =
497 new_kv_store(&database_url, "profile_tag_map", surfnet_id)?;
498 let simulated_transaction_profiles_db: Box<dyn Storage<String, KeyedProfileResult>> =
499 new_kv_store(&database_url, "simulated_transaction_profiles", surfnet_id)?;
500 let executed_transaction_profiles_db: Box<dyn Storage<String, KeyedProfileResult>> =
501 new_kv_store_with_default(
502 &database_url,
503 "executed_transaction_profiles",
504 surfnet_id,
505 || Box::new(FifoMap::<String, KeyedProfileResult>::default()),
508 )?;
509 let slot_checkpoint_db: Box<dyn Storage<String, u64>> =
510 new_kv_store(&database_url, "slot_checkpoint", surfnet_id)?;
511
512 let checkpoint_slot = slot_checkpoint_db.get(&"latest_slot".to_string())?;
514 let max_block_slot = blocks_db
515 .into_iter()
516 .unwrap()
517 .max_by_key(|(slot, _): &(u64, BlockHeader)| *slot);
518
519 let chain_tip = match (checkpoint_slot, max_block_slot) {
520 (Some(checkpoint), Some((block_slot, block))) => {
522 if checkpoint > block_slot {
523 BlockIdentifier {
525 index: checkpoint,
526 hash: SyntheticBlockhash::new(checkpoint).to_string(),
527 }
528 } else {
529 BlockIdentifier {
531 index: block.block_height,
532 hash: block.hash,
533 }
534 }
535 }
536 (Some(checkpoint), None) => BlockIdentifier {
537 index: checkpoint,
538 hash: SyntheticBlockhash::new(checkpoint).to_string(),
539 },
540 (None, Some((_, block))) => BlockIdentifier {
541 index: block.block_height,
542 hash: block.hash,
543 },
544 (None, None) => BlockIdentifier::zero(),
545 };
546
547 let transactions_processed = transactions_db.count()?;
549
550 let mut svm = Self {
551 inner,
552 remote_rpc_url: None,
553 chain_tip,
554 blocks: blocks_db,
555 transactions: transactions_db,
556 perf_samples: VecDeque::new(),
557 transactions_processed,
558 simnet_events_tx,
559 geyser_events_tx,
560 latest_epoch_info: EpochInfo {
561 epoch: 0,
562 slot_index: 0,
563 slots_in_epoch: SLOTS_PER_EPOCH,
564 absolute_slot: 0,
565 block_height: 0,
566 transaction_count: None,
567 },
568 transactions_queued_for_confirmation: VecDeque::new(),
569 transactions_queued_for_finalization: VecDeque::new(),
570 signature_subscriptions: HashMap::new(),
571 account_subscriptions: HashMap::new(),
572 program_subscriptions: HashMap::new(),
573 slot_subscriptions: Vec::new(),
574 profile_tag_map: profile_tag_map_db,
575 simulated_transaction_profiles: simulated_transaction_profiles_db,
576 executed_transaction_profiles: executed_transaction_profiles_db,
577 logs_subscriptions: Vec::new(),
578 snapshot_subscriptions: Vec::new(),
579 updated_at: Utc::now().timestamp_millis() as u64,
580 slot_time: DEFAULT_SLOT_TIME_MS,
581 start_time: SystemTime::now(),
582 accounts_by_owner: accounts_by_owner_db,
583 account_associated_data: account_associated_data_db,
584 token_accounts: token_accounts_db,
585 token_mints: token_mints_db,
586 token_accounts_by_owner: token_accounts_by_owner_db,
587 token_accounts_by_delegate: token_accounts_by_delegate_db,
588 token_accounts_by_mint: token_accounts_by_mint_db,
589 total_supply: 0,
590 circulating_supply: 0,
591 non_circulating_supply: 0,
592 non_circulating_accounts: Vec::new(),
593 genesis_config: GenesisConfig::default(),
594 inflation: Inflation::default(),
595 write_version: 0,
596 registered_idls: registered_idls_db,
597 feature_set,
598 instruction_profiling_enabled: true,
599 max_profiles: DEFAULT_PROFILING_MAP_CAPACITY,
600 runbook_executions: Vec::new(),
601 account_update_slots: HashMap::new(),
602 streamed_accounts: streamed_accounts_db,
603 recent_blockhashes: VecDeque::new(),
604 scheduled_overrides: scheduled_overrides_db,
605 closed_accounts: HashSet::new(),
606 genesis_slot: 0, genesis_updated_at: Utc::now().timestamp_millis() as u64,
608 slot_checkpoint: slot_checkpoint_db,
609 last_checkpoint_slot: 0,
610 };
611
612 svm.chain_tip = svm.new_blockhash();
614
615 Ok((svm, simnet_events_rx, geyser_events_rx))
616 }
617
618 pub fn apply_feature_config(&mut self, config: &SvmFeatureConfig) {
626 for pubkey in &config.enable {
628 self.feature_set.activate(pubkey, 0);
629 }
630
631 for pubkey in &config.disable {
633 self.feature_set.deactivate(pubkey);
634 }
635
636 self.inner.apply_feature_config(self.feature_set.clone());
638 }
639
640 pub fn increment_write_version(&mut self) -> u64 {
641 self.write_version += 1;
642 self.write_version
643 }
644
645 pub fn initialize(
654 &mut self,
655 epoch_info: EpochInfo,
656 epoch_schedule: EpochSchedule,
657 slot_time: u64,
658 remote_ctx: &Option<SurfnetRemoteClient>,
659 do_profile_instructions: bool,
660 log_bytes_limit: Option<usize>,
661 ) {
662 self.chain_tip = self.new_blockhash();
663 self.latest_epoch_info = epoch_info.clone();
664 self.genesis_slot = epoch_info.absolute_slot;
667 self.updated_at = Utc::now().timestamp_millis() as u64;
668 self.genesis_updated_at = self.updated_at;
670 self.slot_time = slot_time;
671 self.instruction_profiling_enabled = do_profile_instructions;
672 self.set_profiling_map_capacity(self.max_profiles);
673 self.inner.set_log_bytes_limit(log_bytes_limit);
674
675 let registry = TemplateRegistry::new();
676 for (_, template) in registry.templates.into_iter() {
677 let _ = self.register_idl(template.idl, None);
678 }
679
680 self.inner.set_sysvar(&epoch_schedule);
681
682 if let Some(remote_client) = remote_ctx {
683 let _ = self
684 .simnet_events_tx
685 .send(SimnetEvent::Connected(remote_client.client.url()));
686 }
687 let _ = self
688 .simnet_events_tx
689 .send(SimnetEvent::EpochInfoUpdate(epoch_info));
690
691 self.reconstruct_sysvars();
693 }
694
695 pub fn set_profile_instructions(&mut self, do_profile_instructions: bool) {
696 self.instruction_profiling_enabled = do_profile_instructions;
697 }
698
699 pub fn set_profiling_map_capacity(&mut self, capacity: usize) {
700 let clamped_capacity = max(1, capacity);
701 self.max_profiles = clamped_capacity;
702 let is_on_disk_db = self.inner.db.is_some();
703 if !is_on_disk_db {
704 self.executed_transaction_profiles = Box::new(FifoMap::new(clamped_capacity));
706 }
707 }
708
709 #[allow(clippy::result_large_err)]
718 pub fn airdrop(&mut self, pubkey: &Pubkey, lamports: u64) -> SurfpoolResult<TransactionResult> {
719 let airdrop_pubkey = self.inner.airdrop_pubkey();
721
722 let airdrop_account_before = self
723 .get_account(&airdrop_pubkey)?
724 .unwrap_or_else(|| Account::default());
725 let recipient_account_before = self
726 .get_account(pubkey)?
727 .unwrap_or_else(|| Account::default());
728 let system_account_before = self
729 .get_account(&system_program::id())?
730 .unwrap_or_else(|| Account::default());
731
732 let res = self.inner.airdrop(pubkey, lamports);
733 let (status_tx, _rx) = unbounded();
734 if let Ok(ref tx_result) = res {
735 let slot = self.latest_epoch_info.absolute_slot;
736 let airdrop_account_after = self
738 .get_account(&airdrop_pubkey)?
739 .unwrap_or_else(|| Account::default());
740 let recipient_account_after = self
741 .get_account(pubkey)?
742 .unwrap_or_else(|| Account::default());
743 let system_account_after = self
744 .get_account(&system_program::id())?
745 .unwrap_or_else(|| Account::default());
746
747 let tx = VersionedTransaction {
749 signatures: vec![tx_result.signature],
750 message: VersionedMessage::Legacy(Message::new(
751 &[system_instruction::transfer(
752 &airdrop_pubkey,
753 pubkey,
754 lamports,
755 )],
756 Some(&airdrop_pubkey),
757 )),
758 };
759
760 self.transactions.store(
761 tx.get_signature().to_string(),
762 SurfnetTransactionStatus::processed(
763 TransactionWithStatusMeta {
764 slot,
765 transaction: tx.clone(),
766 meta: TransactionStatusMeta {
767 status: Ok(()),
768 fee: 5000,
769 pre_balances: vec![
770 airdrop_account_before.lamports,
771 recipient_account_before.lamports,
772 system_account_before.lamports,
773 ],
774 post_balances: vec![
775 airdrop_account_after.lamports,
776 recipient_account_after.lamports,
777 system_account_after.lamports,
778 ],
779 inner_instructions: Some(vec![]),
780 log_messages: Some(tx_result.logs.clone()),
781 pre_token_balances: Some(vec![]),
782 post_token_balances: Some(vec![]),
783 rewards: Some(vec![]),
784 loaded_addresses: LoadedAddresses::default(),
785 return_data: Some(tx_result.return_data.clone()),
786 compute_units_consumed: Some(tx_result.compute_units_consumed),
787 cost_units: None,
788 },
789 },
790 HashSet::from([*pubkey]),
791 ),
792 )?;
793 self.notify_signature_subscribers(
794 SignatureSubscriptionType::processed(),
795 tx.get_signature(),
796 slot,
797 None,
798 );
799 self.notify_logs_subscribers(
800 tx.get_signature(),
801 None,
802 tx_result.logs.clone(),
803 CommitmentLevel::Processed,
804 );
805 self.transactions_queued_for_confirmation
806 .push_back((tx, status_tx.clone(), None));
807 let account = self.get_account(pubkey)?.unwrap();
808 self.set_account(pubkey, account)?;
809 }
810 Ok(res)
811 }
812
813 pub fn airdrop_pubkeys(&mut self, lamports: u64, addresses: &[Pubkey]) {
819 for recipient in addresses {
820 match self.airdrop(recipient, lamports) {
821 Ok(_) => {
822 let _ = self.simnet_events_tx.send(SimnetEvent::info(format!(
823 "Genesis airdrop successful {}: {}",
824 recipient, lamports
825 )));
826 }
827 Err(e) => {
828 let _ = self.simnet_events_tx.send(SimnetEvent::error(format!(
829 "Genesis airdrop failed {}: {}",
830 recipient, e
831 )));
832 }
833 };
834 }
835 }
836
837 pub const fn get_latest_absolute_slot(&self) -> Slot {
839 self.latest_epoch_info.absolute_slot
840 }
841
842 pub fn latest_blockhash(&self) -> solana_hash::Hash {
844 Hash::from_str(&self.chain_tip.hash).expect("Invalid blockhash")
845 }
846
847 pub fn latest_epoch_info(&self) -> EpochInfo {
849 self.latest_epoch_info.clone()
850 }
851
852 pub fn calculate_block_time_for_slot(&self, slot: Slot) -> u64 {
855 let slots_since_genesis = slot.saturating_sub(self.genesis_slot);
857 self.genesis_updated_at + (slots_since_genesis * self.slot_time)
858 }
859
860 pub fn is_slot_in_valid_range(&self, slot: Slot) -> bool {
869 let latest_slot = self.get_latest_absolute_slot();
870 slot >= self.genesis_slot && slot <= latest_slot
871 }
872
873 pub fn get_block_or_reconstruct(&self, slot: Slot) -> SurfpoolResult<Option<BlockHeader>> {
884 match self.blocks.get(&slot)? {
885 Some(block) => Ok(Some(block)),
886 None => {
887 if self.is_slot_in_valid_range(slot) {
888 Ok(Some(self.reconstruct_empty_block(slot)))
889 } else {
890 Ok(None)
891 }
892 }
893 }
894 }
895
896 pub fn reconstruct_empty_block(&self, slot: Slot) -> BlockHeader {
899 let block_height = slot;
900 BlockHeader {
901 hash: SyntheticBlockhash::new(block_height).to_string(),
902 previous_blockhash: SyntheticBlockhash::new(block_height.saturating_sub(1)).to_string(),
903 parent_slot: slot.saturating_sub(1),
904 block_time: (self.calculate_block_time_for_slot(slot) / 1_000) as i64,
905 block_height,
906 signatures: vec![],
907 }
908 }
909
910 pub fn get_account_from_feature_set(&self, pubkey: &Pubkey) -> Option<Account> {
911 self.feature_set.active().get(pubkey).map(|_| {
915 let feature_bytes = bincode::serialize(&FEATURE).unwrap();
916 let lamports = self
917 .inner
918 .minimum_balance_for_rent_exemption(feature_bytes.len());
919 Account {
920 lamports,
921 data: feature_bytes,
922 owner: solana_sdk_ids::feature::id(),
923 executable: false,
924 rent_epoch: 0,
925 }
926 })
927 }
928
929 #[allow(deprecated)]
936 pub fn reconstruct_sysvars(&mut self) {
937 use solana_slot_hashes::SlotHashes;
938 use solana_sysvar::recent_blockhashes::{IterItem, RecentBlockhashes};
939
940 let current_index = self.chain_tip.index;
941 let current_absolute_slot = self.get_latest_absolute_slot();
942
943 let start_index = current_index.saturating_sub(MAX_RECENT_BLOCKHASHES_STANDARD as u64 - 1);
945
946 let synthetic_hashes: Vec<_> = (start_index..=current_index)
949 .rev()
950 .map(SyntheticBlockhash::new)
951 .collect();
952
953 let recent_blockhashes_vec: Vec<_> = synthetic_hashes
955 .iter()
956 .enumerate()
957 .map(|(index, hash)| IterItem(index as u64, hash.hash(), 0))
958 .collect();
959 let recent_blockhashes = RecentBlockhashes::from_iter(recent_blockhashes_vec);
960 self.inner.set_sysvar(&recent_blockhashes);
961
962 let start_absolute_slot = start_index + self.genesis_slot;
964 let slot_hashes_vec: Vec<_> = (start_absolute_slot..=current_absolute_slot)
965 .rev()
966 .zip(synthetic_hashes.iter())
967 .map(|(slot, hash)| (slot, *hash.hash()))
968 .collect();
969 let slot_hashes = SlotHashes::new(&slot_hashes_vec);
970 self.inner.set_sysvar(&slot_hashes);
971
972 let unix_timestamp = self.calculate_block_time_for_slot(current_absolute_slot) / 1_000;
974 let clock = Clock {
975 slot: current_absolute_slot,
976 epoch: self.latest_epoch_info.epoch,
977 unix_timestamp: unix_timestamp as i64,
978 epoch_start_timestamp: 0,
979 leader_schedule_epoch: 0,
980 };
981 self.inner.set_sysvar(&clock);
982 }
983
984 #[allow(deprecated)]
989 fn new_blockhash(&mut self) -> BlockIdentifier {
990 use solana_slot_hashes::SlotHashes;
991 use solana_sysvar::recent_blockhashes::{IterItem, RecentBlockhashes};
992 let recent_blockhashes_backup = self.inner.get_sysvar::<RecentBlockhashes>();
994 let num_blockhashes_expected = recent_blockhashes_backup
995 .len()
996 .min(MAX_RECENT_BLOCKHASHES_STANDARD);
997 self.inner.expire_blockhash();
1000 let mut recent_blockhashes = Vec::with_capacity(num_blockhashes_expected);
1002 let recent_blockhashes_overriden = self.inner.get_sysvar::<RecentBlockhashes>();
1003 let latest_entry = recent_blockhashes_overriden
1004 .first()
1005 .expect("Latest blockhash not found");
1006
1007 let new_synthetic_blockhash = SyntheticBlockhash::new(self.chain_tip.index);
1008 let new_synthetic_blockhash_str = new_synthetic_blockhash.to_string();
1009
1010 recent_blockhashes.push(IterItem(
1011 0,
1012 new_synthetic_blockhash.hash(),
1013 latest_entry.fee_calculator.lamports_per_signature,
1014 ));
1015
1016 for (index, entry) in recent_blockhashes_backup.iter().enumerate() {
1018 if recent_blockhashes.len() >= MAX_RECENT_BLOCKHASHES_STANDARD {
1019 break;
1020 }
1021 recent_blockhashes.push(IterItem(
1022 (index + 1) as u64,
1023 &entry.blockhash,
1024 entry.fee_calculator.lamports_per_signature,
1025 ));
1026 }
1027
1028 self.inner
1029 .set_sysvar(&RecentBlockhashes::from_iter(recent_blockhashes));
1030
1031 let mut slot_hashes = self.inner.get_sysvar::<SlotHashes>();
1032 slot_hashes.add(
1033 self.get_latest_absolute_slot() + 1,
1034 *new_synthetic_blockhash.hash(),
1035 );
1036 self.inner.set_sysvar(&SlotHashes::new(&slot_hashes));
1037
1038 BlockIdentifier::new(
1039 self.chain_tip.index + 1,
1040 new_synthetic_blockhash_str.as_str(),
1041 )
1042 }
1043
1044 pub fn check_blockhash_is_recent(&self, recent_blockhash: &Hash) -> bool {
1052 #[allow(deprecated)]
1053 self.inner
1054 .get_sysvar::<solana_sysvar::recent_blockhashes::RecentBlockhashes>()
1055 .iter()
1056 .any(|entry| entry.blockhash == *recent_blockhash)
1057 }
1058
1059 pub fn validate_transaction_blockhash(&self, tx: &VersionedTransaction) -> bool {
1069 let recent_blockhash = tx.message.recent_blockhash();
1070
1071 let some_nonce_account_index = tx
1072 .message
1073 .instructions()
1074 .get(solana_nonce::NONCED_TX_MARKER_IX_INDEX as usize)
1075 .filter(|instruction| {
1076 matches!(
1077 tx.message.static_account_keys().get(instruction.program_id_index as usize),
1078 Some(program_id) if system_program::check_id(program_id)
1079 ) && is_advance_nonce_instruction_data(&instruction.data)
1080 })
1081 .map(|instruction| {
1082 instruction.accounts.get(0)
1084 });
1085
1086 debug!(
1087 "Validating tx blockhash: {}; is nonce tx?: {}",
1088 recent_blockhash,
1089 some_nonce_account_index.is_some()
1090 );
1091
1092 if let Some(nonce_account_index) = some_nonce_account_index {
1093 trace!(
1094 "Nonce tx detected. Nonce account index: {:?}",
1095 nonce_account_index
1096 );
1097 let Some(nonce_account_index) = nonce_account_index else {
1098 return false;
1099 };
1100
1101 let Some(nonce_account_pubkey) = tx
1102 .message
1103 .static_account_keys()
1104 .get(*nonce_account_index as usize)
1105 else {
1106 return false;
1107 };
1108
1109 trace!("Nonce account pubkey: {:?}", nonce_account_pubkey,);
1110
1111 let Ok(Some(nonce_account)) = self.get_account(nonce_account_pubkey) else {
1114 return false;
1115 };
1116 trace!("Nonce account: {:?}", nonce_account);
1117
1118 let Some(nonce_data) =
1119 bincode::deserialize::<solana_nonce::versions::Versions>(&nonce_account.data).ok()
1120 else {
1121 return false;
1122 };
1123 trace!("Nonce account data: {:?}", nonce_data);
1124
1125 let nonce_state = nonce_data.state();
1126 let initialized_state = match nonce_state {
1127 solana_nonce::state::State::Uninitialized => return false,
1128 solana_nonce::state::State::Initialized(data) => data,
1129 };
1130 return initialized_state.blockhash() == *recent_blockhash;
1131 } else {
1132 self.check_blockhash_is_recent(recent_blockhash)
1133 }
1134 }
1135
1136 pub fn sigverify(&self, tx: &VersionedTransaction) -> Result<(), FailedTransactionMetadata> {
1144 let signature = tx.signatures[0];
1145
1146 if tx.verify_with_results().iter().any(|valid| !*valid) {
1147 return Err(FailedTransactionMetadata {
1148 err: TransactionError::SignatureFailure,
1149 meta: TransactionMetadata::default(),
1150 });
1151 }
1152
1153 if matches!(
1154 self.transactions.get(&signature.to_string()),
1155 Ok(Some(SurfnetTransactionStatus::Processed(_)))
1156 ) {
1157 return Err(FailedTransactionMetadata {
1158 err: TransactionError::AlreadyProcessed,
1159 meta: TransactionMetadata::default(),
1160 });
1161 }
1162 Ok(())
1163 }
1164
1165 pub fn set_account(&mut self, pubkey: &Pubkey, account: Account) -> SurfpoolResult<()> {
1174 self.inner
1175 .set_account(*pubkey, account.clone())
1176 .map_err(|e| SurfpoolError::set_account(*pubkey, e))?;
1177
1178 self.account_update_slots
1179 .insert(*pubkey, self.get_latest_absolute_slot());
1180
1181 self.update_account_registries(pubkey, &account)?;
1183
1184 self.notify_account_subscribers(pubkey, &account);
1186
1187 self.notify_program_subscribers(pubkey, &account);
1189
1190 let _ = self
1191 .simnet_events_tx
1192 .send(SimnetEvent::account_update(*pubkey));
1193 Ok(())
1194 }
1195
1196 pub fn update_account_registries(
1197 &mut self,
1198 pubkey: &Pubkey,
1199 account: &Account,
1200 ) -> SurfpoolResult<()> {
1201 let is_deleted_account = account == &Account::default();
1202
1203 if is_deleted_account {
1206 self.inner.delete_account_in_db(pubkey)?;
1208 } else {
1209 self.inner
1211 .set_account_in_db(*pubkey, account.clone().into())?;
1212 }
1213
1214 if is_deleted_account {
1215 self.closed_accounts.insert(*pubkey);
1216 if let Some(old_account) = self.get_account(pubkey)? {
1217 self.remove_from_indexes(pubkey, &old_account)?;
1218 }
1219 return Ok(());
1220 }
1221
1222 if let Some(old_account) = self.get_account(pubkey)? {
1224 self.remove_from_indexes(pubkey, &old_account)?;
1225 }
1226 let owner_key = account.owner.to_string();
1228 let pubkey_str = pubkey.to_string();
1229 let mut owner_accounts = self
1230 .accounts_by_owner
1231 .get(&owner_key)
1232 .ok()
1233 .flatten()
1234 .unwrap_or_default();
1235 if !owner_accounts.contains(&pubkey_str) {
1236 owner_accounts.push(pubkey_str.clone());
1237 self.accounts_by_owner.store(owner_key, owner_accounts)?;
1238 }
1239
1240 if is_supported_token_program(&account.owner) {
1242 if let Ok(token_account) = TokenAccount::unpack(&account.data) {
1243 let owner_key = token_account.owner().to_string();
1245 let mut token_owner_accounts = self
1246 .token_accounts_by_owner
1247 .get(&owner_key)
1248 .ok()
1249 .flatten()
1250 .unwrap_or_default();
1251 if !token_owner_accounts.contains(&pubkey_str) {
1252 token_owner_accounts.push(pubkey_str.clone());
1253 self.token_accounts_by_owner
1254 .store(owner_key, token_owner_accounts)?;
1255 }
1256
1257 let mint_key = token_account.mint().to_string();
1259 let mut mint_accounts = self
1260 .token_accounts_by_mint
1261 .get(&mint_key)
1262 .ok()
1263 .flatten()
1264 .unwrap_or_default();
1265 if !mint_accounts.contains(&pubkey_str) {
1266 mint_accounts.push(pubkey_str.clone());
1267 self.token_accounts_by_mint.store(mint_key, mint_accounts)?;
1268 }
1269
1270 if let COption::Some(delegate) = token_account.delegate() {
1271 let delegate_key = delegate.to_string();
1272 let mut delegate_accounts = self
1273 .token_accounts_by_delegate
1274 .get(&delegate_key)
1275 .ok()
1276 .flatten()
1277 .unwrap_or_default();
1278 if !delegate_accounts.contains(&pubkey_str) {
1279 delegate_accounts.push(pubkey_str);
1280 self.token_accounts_by_delegate
1281 .store(delegate_key, delegate_accounts)?;
1282 }
1283 }
1284 self.token_accounts
1285 .store(pubkey.to_string(), token_account)?;
1286 }
1287
1288 if let Ok(mint_account) = MintAccount::unpack(&account.data) {
1289 self.token_mints.store(pubkey.to_string(), mint_account)?;
1290 }
1291
1292 if let Ok(mint) =
1293 StateWithExtensions::<spl_token_2022_interface::state::Mint>::unpack(&account.data)
1294 {
1295 let unix_timestamp = self.inner.get_sysvar::<Clock>().unix_timestamp;
1296 let interest_bearing_config = mint
1297 .get_extension::<InterestBearingConfig>()
1298 .map(|x| (*x, unix_timestamp))
1299 .ok();
1300 let scaled_ui_amount_config = mint
1301 .get_extension::<ScaledUiAmountConfig>()
1302 .map(|x| (*x, unix_timestamp))
1303 .ok();
1304 let additional_data: SerializableAccountAdditionalData = AccountAdditionalDataV3 {
1305 spl_token_additional_data: Some(SplTokenAdditionalDataV2 {
1306 decimals: mint.base.decimals,
1307 interest_bearing_config,
1308 scaled_ui_amount_config,
1309 }),
1310 }
1311 .into();
1312 self.account_associated_data
1313 .store(pubkey.to_string(), additional_data)?;
1314 };
1315 }
1316 Ok(())
1317 }
1318
1319 fn remove_from_indexes(
1320 &mut self,
1321 pubkey: &Pubkey,
1322 old_account: &Account,
1323 ) -> SurfpoolResult<()> {
1324 let owner_key = old_account.owner.to_string();
1325 let pubkey_str = pubkey.to_string();
1326 if let Some(mut accounts) = self.accounts_by_owner.get(&owner_key).ok().flatten() {
1327 accounts.retain(|pk| pk != &pubkey_str);
1328 if accounts.is_empty() {
1329 self.accounts_by_owner.take(&owner_key)?;
1330 } else {
1331 self.accounts_by_owner.store(owner_key, accounts)?;
1332 }
1333 }
1334
1335 if is_supported_token_program(&old_account.owner) {
1337 if let Some(old_token_account) = self.token_accounts.take(&pubkey.to_string())? {
1338 let owner_key = old_token_account.owner().to_string();
1339 if let Some(mut accounts) =
1340 self.token_accounts_by_owner.get(&owner_key).ok().flatten()
1341 {
1342 accounts.retain(|pk| pk != &pubkey_str);
1343 if accounts.is_empty() {
1344 self.token_accounts_by_owner.take(&owner_key)?;
1345 } else {
1346 self.token_accounts_by_owner.store(owner_key, accounts)?;
1347 }
1348 }
1349
1350 let mint_key = old_token_account.mint().to_string();
1351 if let Some(mut accounts) =
1352 self.token_accounts_by_mint.get(&mint_key).ok().flatten()
1353 {
1354 accounts.retain(|pk| pk != &pubkey_str);
1355 if accounts.is_empty() {
1356 self.token_accounts_by_mint.take(&mint_key)?;
1357 } else {
1358 self.token_accounts_by_mint.store(mint_key, accounts)?;
1359 }
1360 }
1361
1362 if let COption::Some(delegate) = old_token_account.delegate() {
1363 let delegate_key = delegate.to_string();
1364 if let Some(mut accounts) = self
1365 .token_accounts_by_delegate
1366 .get(&delegate_key)
1367 .ok()
1368 .flatten()
1369 {
1370 accounts.retain(|pk| pk != &pubkey_str);
1371 if accounts.is_empty() {
1372 self.token_accounts_by_delegate.take(&delegate_key)?;
1373 } else {
1374 self.token_accounts_by_delegate
1375 .store(delegate_key, accounts)?;
1376 }
1377 }
1378 }
1379 }
1380 }
1381 Ok(())
1382 }
1383
1384 pub fn reset_network(&mut self, epoch_info: EpochInfo) -> SurfpoolResult<()> {
1385 self.inner.reset(self.feature_set.clone())?;
1386
1387 let native_mint_account = self
1388 .inner
1389 .get_account(&spl_token_interface::native_mint::ID)?
1390 .unwrap();
1391
1392 let native_mint_associated_data = {
1393 let mint = StateWithExtensions::<spl_token_2022_interface::state::Mint>::unpack(
1394 &native_mint_account.data,
1395 )
1396 .unwrap();
1397 let unix_timestamp = self.inner.get_sysvar::<Clock>().unix_timestamp;
1398 let interest_bearing_config = mint
1399 .get_extension::<InterestBearingConfig>()
1400 .map(|x| (*x, unix_timestamp))
1401 .ok();
1402 let scaled_ui_amount_config = mint
1403 .get_extension::<ScaledUiAmountConfig>()
1404 .map(|x| (*x, unix_timestamp))
1405 .ok();
1406 AccountAdditionalDataV3 {
1407 spl_token_additional_data: Some(SplTokenAdditionalDataV2 {
1408 decimals: mint.base.decimals,
1409 interest_bearing_config,
1410 scaled_ui_amount_config,
1411 }),
1412 }
1413 };
1414
1415 let parsed_mint_account = MintAccount::unpack(&native_mint_account.data).unwrap();
1416
1417 self.blocks.clear()?;
1418 self.transactions.clear()?;
1419 self.transactions_queued_for_confirmation.clear();
1420 self.transactions_queued_for_finalization.clear();
1421 self.perf_samples.clear();
1422 self.transactions_processed = 0;
1423 self.profile_tag_map.clear()?;
1424 self.simulated_transaction_profiles.clear()?;
1425 self.executed_transaction_profiles.clear()?;
1426 self.accounts_by_owner.clear()?;
1427 self.accounts_by_owner.store(
1428 native_mint_account.owner.to_string(),
1429 vec![spl_token_interface::native_mint::ID.to_string()],
1430 )?;
1431 self.account_associated_data.clear()?;
1432 self.account_associated_data.store(
1433 spl_token_interface::native_mint::ID.to_string(),
1434 native_mint_associated_data.into(),
1435 )?;
1436 self.token_accounts.clear()?;
1437 self.token_mints.clear()?;
1438 self.token_mints.store(
1439 spl_token_interface::native_mint::ID.to_string(),
1440 parsed_mint_account,
1441 )?;
1442 self.token_accounts_by_owner.clear()?;
1443 self.token_accounts_by_delegate.clear()?;
1444 self.token_accounts_by_mint.clear()?;
1445 self.non_circulating_accounts.clear();
1446 self.registered_idls.clear()?;
1447 self.runbook_executions.clear();
1448 self.streamed_accounts.clear()?;
1449 self.scheduled_overrides.clear()?;
1450
1451 let current_time = chrono::Utc::now().timestamp_millis() as u64;
1452 self.updated_at = current_time;
1453 self.genesis_updated_at = current_time;
1454 self.latest_epoch_info = epoch_info.clone();
1455 self.genesis_slot = epoch_info.absolute_slot;
1457 let chain_tip_hash = SyntheticBlockhash::new(epoch_info.block_height).to_string();
1458 self.chain_tip = BlockIdentifier::new(epoch_info.block_height, chain_tip_hash.as_str());
1459 self.reconstruct_sysvars();
1461 self.slot_checkpoint.clear()?;
1463 self.last_checkpoint_slot = self.genesis_slot;
1464 self.recent_blockhashes.clear();
1465
1466 Ok(())
1467 }
1468
1469 pub fn reset_account(
1470 &mut self,
1471 pubkey: &Pubkey,
1472 include_owned_accounts: bool,
1473 ) -> SurfpoolResult<()> {
1474 let Some(account) = self.get_account(pubkey)? else {
1475 return Ok(());
1476 };
1477
1478 if account.executable {
1479 if account.owner == solana_sdk_ids::bpf_loader_upgradeable::id() {
1481 let program_data_pubkey =
1482 solana_loader_v3_interface::get_program_data_address(pubkey);
1483
1484 self.purge_account_from_cache(&account, &program_data_pubkey)?;
1486 }
1487 }
1488 if include_owned_accounts {
1489 let owned_accounts = self.get_account_owned_by(pubkey)?;
1490 for (owned_pubkey, _) in owned_accounts {
1491 self.purge_account_from_cache(&account, &owned_pubkey)?;
1493 }
1494 }
1495 self.purge_account_from_cache(&account, pubkey)?;
1497 Ok(())
1498 }
1499
1500 fn purge_account_from_cache(
1501 &mut self,
1502 account: &Account,
1503 pubkey: &Pubkey,
1504 ) -> SurfpoolResult<()> {
1505 self.remove_from_indexes(pubkey, account)?;
1506
1507 self.inner.delete_account(pubkey)?;
1508
1509 Ok(())
1510 }
1511
1512 #[allow(clippy::result_large_err)]
1526 pub fn send_transaction(
1527 &mut self,
1528 tx: VersionedTransaction,
1529 cu_analysis_enabled: bool,
1530 sigverify: bool,
1531 ) -> TransactionResult {
1532 if sigverify {
1533 self.sigverify(&tx)?;
1534 }
1535
1536 if cu_analysis_enabled {
1537 let estimation_result = self.estimate_compute_units(&tx);
1538 let _ = self.simnet_events_tx.try_send(SimnetEvent::info(format!(
1539 "CU Estimation for tx: {} | Consumed: {} | Success: {} | Logs: {:?} | Error: {:?}",
1540 tx.signatures
1541 .first()
1542 .map_or_else(|| "N/A".to_string(), |s| s.to_string()),
1543 estimation_result.compute_units_consumed,
1544 estimation_result.success,
1545 estimation_result.log_messages,
1546 estimation_result.error_message
1547 )));
1548 }
1549 self.transactions_processed += 1;
1550
1551 if !self.validate_transaction_blockhash(&tx) {
1552 let meta = TransactionMetadata::default();
1553 let err = solana_transaction_error::TransactionError::BlockhashNotFound;
1554
1555 let transaction_meta = convert_transaction_metadata_from_canonical(&meta);
1556
1557 let _ = self
1558 .simnet_events_tx
1559 .try_send(SimnetEvent::transaction_processed(
1560 transaction_meta,
1561 Some(err.clone()),
1562 ));
1563 return Err(FailedTransactionMetadata { err, meta });
1564 }
1565
1566 match self.inner.send_transaction(tx.clone()) {
1567 Ok(res) => Ok(res),
1568 Err(tx_failure) => {
1569 let transaction_meta =
1570 convert_transaction_metadata_from_canonical(&tx_failure.meta);
1571
1572 let _ = self
1573 .simnet_events_tx
1574 .try_send(SimnetEvent::transaction_processed(
1575 transaction_meta,
1576 Some(tx_failure.err.clone()),
1577 ));
1578 Err(tx_failure)
1579 }
1580 }
1581 }
1582
1583 pub fn estimate_compute_units(
1593 &self,
1594 transaction: &VersionedTransaction,
1595 ) -> ComputeUnitsEstimationResult {
1596 if !self.validate_transaction_blockhash(transaction) {
1597 return ComputeUnitsEstimationResult {
1598 success: false,
1599 compute_units_consumed: 0,
1600 log_messages: None,
1601 error_message: Some(
1602 solana_transaction_error::TransactionError::BlockhashNotFound.to_string(),
1603 ),
1604 };
1605 }
1606
1607 match self.inner.simulate_transaction(transaction.clone()) {
1608 Ok(sim_info) => ComputeUnitsEstimationResult {
1609 success: true,
1610 compute_units_consumed: sim_info.meta.compute_units_consumed,
1611 log_messages: Some(sim_info.meta.logs),
1612 error_message: None,
1613 },
1614 Err(failed_meta) => ComputeUnitsEstimationResult {
1615 success: false,
1616 compute_units_consumed: failed_meta.meta.compute_units_consumed,
1617 log_messages: Some(failed_meta.meta.logs),
1618 error_message: Some(failed_meta.err.to_string()),
1619 },
1620 }
1621 }
1622
1623 #[allow(clippy::result_large_err)]
1631 pub fn simulate_transaction(
1632 &self,
1633 tx: VersionedTransaction,
1634 sigverify: bool,
1635 ) -> Result<SimulatedTransactionInfo, FailedTransactionMetadata> {
1636 if sigverify {
1637 self.sigverify(&tx)?;
1638 }
1639
1640 if !self.validate_transaction_blockhash(&tx) {
1641 let meta = TransactionMetadata::default();
1642 let err = TransactionError::BlockhashNotFound;
1643
1644 return Err(FailedTransactionMetadata { err, meta });
1645 }
1646 self.inner.simulate_transaction(tx)
1647 }
1648
1649 fn confirm_transactions(&mut self) -> Result<(Vec<Signature>, HashSet<Pubkey>), SurfpoolError> {
1654 let mut confirmed_transactions = vec![];
1655 let slot = self.latest_epoch_info.slot_index;
1656 let current_slot = self.latest_epoch_info.absolute_slot;
1657
1658 let mut all_mutated_account_keys = HashSet::new();
1659
1660 while let Some((tx, status_tx, error)) =
1661 self.transactions_queued_for_confirmation.pop_front()
1662 {
1663 let _ = status_tx.try_send(TransactionStatusEvent::Success(
1664 TransactionConfirmationStatus::Confirmed,
1665 ));
1666 let signature = tx.signatures[0];
1667 let finalized_at = self.latest_epoch_info.absolute_slot + FINALIZATION_SLOT_THRESHOLD;
1668 self.transactions_queued_for_finalization.push_back((
1669 finalized_at,
1670 tx,
1671 status_tx,
1672 error.clone(),
1673 ));
1674
1675 self.notify_signature_subscribers(
1676 SignatureSubscriptionType::confirmed(),
1677 &signature,
1678 slot,
1679 error,
1680 );
1681
1682 let Some(SurfnetTransactionStatus::Processed(tx_data)) =
1683 self.transactions.get(&signature.to_string()).ok().flatten()
1684 else {
1685 continue;
1686 };
1687 let (tx_with_status_meta, mutated_account_keys) = tx_data.as_ref();
1688 all_mutated_account_keys.extend(mutated_account_keys);
1689
1690 for pubkey in mutated_account_keys {
1691 self.account_update_slots.insert(*pubkey, current_slot);
1692 }
1693
1694 self.notify_logs_subscribers(
1695 &signature,
1696 None,
1697 tx_with_status_meta
1698 .meta
1699 .log_messages
1700 .clone()
1701 .unwrap_or(vec![]),
1702 CommitmentLevel::Confirmed,
1703 );
1704 confirmed_transactions.push(signature);
1705 }
1706
1707 Ok((confirmed_transactions, all_mutated_account_keys))
1708 }
1709
1710 fn finalize_transactions(&mut self) -> Result<(), SurfpoolError> {
1715 let current_slot = self.latest_epoch_info.absolute_slot;
1716 let mut requeue = VecDeque::new();
1717 while let Some((finalized_at, tx, status_tx, error)) =
1718 self.transactions_queued_for_finalization.pop_front()
1719 {
1720 if current_slot >= finalized_at {
1721 let _ = status_tx.try_send(TransactionStatusEvent::Success(
1722 TransactionConfirmationStatus::Finalized,
1723 ));
1724 let signature = &tx.signatures[0];
1725 self.notify_signature_subscribers(
1726 SignatureSubscriptionType::finalized(),
1727 signature,
1728 self.latest_epoch_info.absolute_slot,
1729 error,
1730 );
1731 let Some(SurfnetTransactionStatus::Processed(tx_data)) =
1732 self.transactions.get(&signature.to_string()).ok().flatten()
1733 else {
1734 continue;
1735 };
1736 let (tx_with_status_meta, _) = tx_data.as_ref();
1737 let logs = tx_with_status_meta
1738 .meta
1739 .log_messages
1740 .clone()
1741 .unwrap_or(vec![]);
1742 self.notify_logs_subscribers(signature, None, logs, CommitmentLevel::Finalized);
1743 } else {
1744 requeue.push_back((finalized_at, tx, status_tx, error));
1745 }
1746 }
1747 self.transactions_queued_for_finalization
1749 .append(&mut requeue);
1750
1751 Ok(())
1752 }
1753
1754 pub fn write_account_update(&mut self, account_update: GetAccountResult) {
1759 let init_programdata_account = |program_account: &Account| {
1760 if !program_account.executable {
1761 return None;
1762 }
1763 if !program_account
1764 .owner
1765 .eq(&solana_sdk_ids::bpf_loader_upgradeable::id())
1766 {
1767 return None;
1768 }
1769 let Ok(UpgradeableLoaderState::Program {
1770 programdata_address,
1771 }) = bincode::deserialize::<UpgradeableLoaderState>(&program_account.data)
1772 else {
1773 return None;
1774 };
1775
1776 let programdata_state = UpgradeableLoaderState::ProgramData {
1777 upgrade_authority_address: Some(system_program::id()),
1778 slot: self.get_latest_absolute_slot(),
1779 };
1780 let mut data = bincode::serialize(&programdata_state).unwrap();
1781
1782 data.extend_from_slice(&include_bytes!("../tests/assets/minimum_program.so").to_vec());
1783 let lamports = self.inner.minimum_balance_for_rent_exemption(data.len());
1784 Some((
1785 programdata_address,
1786 Account {
1787 lamports,
1788 data,
1789 owner: solana_sdk_ids::bpf_loader_upgradeable::id(),
1790 executable: false,
1791 rent_epoch: 0,
1792 },
1793 ))
1794 };
1795 match account_update {
1796 GetAccountResult::FoundAccount(pubkey, account, do_update_account) => {
1797 if do_update_account {
1798 if let Some((programdata_address, programdata_account)) =
1799 init_programdata_account(&account)
1800 {
1801 match self.get_account(&programdata_address) {
1802 Ok(None) => {
1803 if let Err(e) =
1804 self.set_account(&programdata_address, programdata_account)
1805 {
1806 let _ = self
1807 .simnet_events_tx
1808 .send(SimnetEvent::error(e.to_string()));
1809 }
1810 }
1811 Ok(Some(_)) => {}
1812 Err(e) => {
1813 let _ = self
1814 .simnet_events_tx
1815 .send(SimnetEvent::error(e.to_string()));
1816 }
1817 }
1818 }
1819 if let Err(e) = self.set_account(&pubkey, account.clone()) {
1820 let _ = self
1821 .simnet_events_tx
1822 .send(SimnetEvent::error(e.to_string()));
1823 }
1824 }
1825 }
1826 GetAccountResult::FoundProgramAccount((pubkey, account), (_, None)) => {
1827 if let Some((programdata_address, programdata_account)) =
1828 init_programdata_account(&account)
1829 {
1830 match self.get_account(&programdata_address) {
1831 Ok(None) => {
1832 if let Err(e) =
1833 self.set_account(&programdata_address, programdata_account)
1834 {
1835 let _ = self
1836 .simnet_events_tx
1837 .send(SimnetEvent::error(e.to_string()));
1838 }
1839 }
1840 Ok(Some(_)) => {}
1841 Err(e) => {
1842 let _ = self
1843 .simnet_events_tx
1844 .send(SimnetEvent::error(e.to_string()));
1845 }
1846 }
1847 }
1848 if let Err(e) = self.set_account(&pubkey, account.clone()) {
1849 let _ = self
1850 .simnet_events_tx
1851 .send(SimnetEvent::error(e.to_string()));
1852 }
1853 }
1854 GetAccountResult::FoundTokenAccount((pubkey, account), (_, None)) => {
1855 if let Err(e) = self.set_account(&pubkey, account.clone()) {
1856 let _ = self
1857 .simnet_events_tx
1858 .send(SimnetEvent::error(e.to_string()));
1859 }
1860 }
1861 GetAccountResult::FoundProgramAccount(
1862 (pubkey, account),
1863 (coupled_pubkey, Some(coupled_account)),
1864 )
1865 | GetAccountResult::FoundTokenAccount(
1866 (pubkey, account),
1867 (coupled_pubkey, Some(coupled_account)),
1868 ) => {
1869 if let Err(e) = self.set_account(&coupled_pubkey, coupled_account.clone()) {
1871 let _ = self
1872 .simnet_events_tx
1873 .send(SimnetEvent::error(e.to_string()));
1874 }
1875 if let Err(e) = self.set_account(&pubkey, account.clone()) {
1876 let _ = self
1877 .simnet_events_tx
1878 .send(SimnetEvent::error(e.to_string()));
1879 }
1880 }
1881 GetAccountResult::None(_) => {}
1882 }
1883 }
1884
1885 pub fn confirm_current_block(&mut self) -> SurfpoolResult<()> {
1886 let slot = self.get_latest_absolute_slot();
1887 let previous_chain_tip = self.chain_tip.clone();
1888 if slot % *GARBAGE_COLLECTION_INTERVAL_SLOTS == 0 {
1889 debug!("Clearing liteSVM cache at slot {}", slot);
1890 self.inner.garbage_collect(self.feature_set.clone());
1891 }
1892 self.chain_tip = self.new_blockhash();
1893 let (confirmed_signatures, all_mutated_account_keys) = self.confirm_transactions()?;
1895 let write_version = self.increment_write_version();
1896
1897 for pubkey in all_mutated_account_keys {
1899 let Some(account) = self.inner.get_account(&pubkey)? else {
1900 continue;
1901 };
1902 self.geyser_events_tx
1903 .send(GeyserEvent::UpdateAccount(
1904 GeyserAccountUpdate::block_update(pubkey, account, slot, write_version),
1905 ))
1906 .ok();
1907 }
1908
1909 let num_transactions = confirmed_signatures.len() as u64;
1910 self.updated_at += self.slot_time;
1911
1912 if !confirmed_signatures.is_empty() {
1915 self.blocks.store(
1916 slot,
1917 BlockHeader {
1918 hash: self.chain_tip.hash.clone(),
1919 previous_blockhash: previous_chain_tip.hash.clone(),
1920 block_time: self.updated_at as i64 / 1_000,
1921 block_height: self.chain_tip.index,
1922 parent_slot: slot,
1923 signatures: confirmed_signatures,
1924 },
1925 )?;
1926 }
1927
1928 if slot.saturating_sub(self.last_checkpoint_slot) >= *CHECKPOINT_INTERVAL_SLOTS {
1931 self.slot_checkpoint
1932 .store("latest_slot".to_string(), slot)?;
1933 self.last_checkpoint_slot = slot;
1934 }
1935
1936 if self.perf_samples.len() > 30 {
1937 self.perf_samples.pop_back();
1938 }
1939 self.perf_samples.push_front(RpcPerfSample {
1940 slot,
1941 num_slots: 1,
1942 sample_period_secs: 1,
1943 num_transactions,
1944 num_non_vote_transactions: Some(num_transactions),
1945 });
1946
1947 self.latest_epoch_info.slot_index += 1;
1948 self.latest_epoch_info.block_height = self.chain_tip.index;
1949 self.latest_epoch_info.absolute_slot += 1;
1950 if self.latest_epoch_info.slot_index > self.latest_epoch_info.slots_in_epoch {
1951 self.latest_epoch_info.slot_index = 0;
1952 self.latest_epoch_info.epoch += 1;
1953 }
1954 let total_transactions = self.latest_epoch_info.transaction_count.unwrap_or(0);
1955 self.latest_epoch_info.transaction_count = Some(total_transactions + num_transactions);
1956
1957 let parent_slot = self.latest_epoch_info.absolute_slot.saturating_sub(1);
1958 let new_slot = self.latest_epoch_info.absolute_slot;
1959 let root = new_slot.saturating_sub(FINALIZATION_SLOT_THRESHOLD);
1960 self.notify_slot_subscribers(new_slot, parent_slot, root);
1961
1962 self.geyser_events_tx
1964 .send(GeyserEvent::UpdateSlotStatus {
1965 slot: new_slot,
1966 parent: Some(parent_slot),
1967 status: GeyserSlotStatus::Confirmed,
1968 })
1969 .ok();
1970
1971 let block_metadata = GeyserBlockMetadata {
1973 slot: new_slot,
1974 blockhash: self.chain_tip.hash.clone(),
1975 parent_slot,
1976 parent_blockhash: previous_chain_tip.hash.clone(),
1977 block_time: Some(self.updated_at as i64 / 1_000),
1978 block_height: Some(self.chain_tip.index),
1979 executed_transaction_count: num_transactions,
1980 entry_count: 1, };
1982 self.geyser_events_tx
1983 .send(GeyserEvent::NotifyBlockMetadata(block_metadata))
1984 .ok();
1985
1986 let entry_hash = solana_hash::Hash::from_str(&self.chain_tip.hash)
1988 .map(|h| h.to_bytes().to_vec())
1989 .unwrap_or_else(|_| vec![0u8; 32]);
1990 let entry_info = GeyserEntryInfo {
1991 slot: new_slot,
1992 index: 0, num_hashes: 1,
1994 hash: entry_hash,
1995 executed_transaction_count: num_transactions,
1996 starting_transaction_index: 0,
1997 };
1998 self.geyser_events_tx
1999 .send(GeyserEvent::NotifyEntry(entry_info))
2000 .ok();
2001
2002 let clock: Clock = Clock {
2003 slot: self.latest_epoch_info.absolute_slot,
2004 epoch: self.latest_epoch_info.epoch,
2005 unix_timestamp: self.updated_at as i64 / 1_000,
2006 epoch_start_timestamp: 0, leader_schedule_epoch: 0, };
2009
2010 let _ = self
2011 .simnet_events_tx
2012 .send(SimnetEvent::SystemClockUpdated(clock.clone()));
2013 self.inner.set_sysvar(&clock);
2014
2015 self.finalize_transactions()?;
2016
2017 if root >= self.genesis_slot {
2020 self.geyser_events_tx
2021 .send(GeyserEvent::UpdateSlotStatus {
2022 slot: root,
2023 parent: root.checked_sub(1),
2024 status: GeyserSlotStatus::Rooted,
2025 })
2026 .ok();
2027 }
2028
2029 let accounts_to_reset: Vec<_> = self.streamed_accounts.into_iter()?.collect();
2031 for (pubkey_str, include_owned_accounts) in accounts_to_reset {
2032 let pubkey = Pubkey::from_str(&pubkey_str)
2033 .map_err(|e| SurfpoolError::invalid_pubkey(&pubkey_str, e.to_string()))?;
2034 self.reset_account(&pubkey, include_owned_accounts)?;
2035 }
2036
2037 Ok(())
2038 }
2039
2040 pub async fn materialize_overrides(
2049 &mut self,
2050 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
2051 ) -> SurfpoolResult<()> {
2052 let current_slot = self.latest_epoch_info.absolute_slot;
2053
2054 let Some(overrides) = self.scheduled_overrides.take(¤t_slot)? else {
2056 return Ok(());
2058 };
2059
2060 debug!(
2061 "Materializing {} override(s) for slot {}",
2062 overrides.len(),
2063 current_slot
2064 );
2065
2066 for override_instance in overrides {
2067 if !override_instance.enabled {
2068 debug!("Skipping disabled override: {}", override_instance.id);
2069 continue;
2070 }
2071
2072 let account_pubkey = match &override_instance.account {
2074 surfpool_types::AccountAddress::Pubkey(pubkey_str) => {
2075 match Pubkey::from_str(pubkey_str) {
2076 Ok(pubkey) => pubkey,
2077 Err(e) => {
2078 warn!(
2079 "Failed to parse pubkey '{}' for override {}: {}",
2080 pubkey_str, override_instance.id, e
2081 );
2082 continue;
2083 }
2084 }
2085 }
2086 surfpool_types::AccountAddress::Pda {
2087 program_id: _,
2088 seeds: _,
2089 } => unimplemented!(),
2090 };
2091
2092 debug!(
2093 "Processing override {} for account {} (label: {:?})",
2094 override_instance.id, account_pubkey, override_instance.label
2095 );
2096
2097 if override_instance.fetch_before_use {
2099 if let Some((client, _)) = remote_ctx {
2100 debug!(
2101 "Fetching fresh account data for {} from remote",
2102 account_pubkey
2103 );
2104
2105 match client
2106 .get_account(&account_pubkey, CommitmentConfig::confirmed())
2107 .await
2108 {
2109 Ok(GetAccountResult::FoundAccount(_pubkey, remote_account, _)) => {
2110 debug!(
2111 "Fetched account {} from remote: {} lamports, {} bytes",
2112 account_pubkey,
2113 remote_account.lamports(),
2114 remote_account.data().len()
2115 );
2116
2117 if let Err(e) = self.inner.set_account(account_pubkey, remote_account) {
2119 warn!(
2120 "Failed to set account {} from remote: {}",
2121 account_pubkey, e
2122 );
2123 }
2124 }
2125 Ok(GetAccountResult::None(_)) => {
2126 debug!("Account {} not found on remote", account_pubkey);
2127 }
2128 Ok(_) => {
2129 debug!("Account {} fetched (other variant)", account_pubkey);
2130 }
2131 Err(e) => {
2132 warn!(
2133 "Failed to fetch account {} from remote: {}",
2134 account_pubkey, e
2135 );
2136 }
2137 }
2138 } else {
2139 debug!(
2140 "fetch_before_use enabled but no remote client available for override {}",
2141 override_instance.id
2142 );
2143 }
2144 }
2145
2146 if !override_instance.values.is_empty() {
2148 debug!(
2149 "Override {} applying {} field modification(s) to account {}",
2150 override_instance.id,
2151 override_instance.values.len(),
2152 account_pubkey
2153 );
2154
2155 let Some(account) = self.inner.get_account(&account_pubkey)? else {
2157 warn!(
2158 "Account {} not found in SVM for override {}, skipping modifications",
2159 account_pubkey, override_instance.id
2160 );
2161 continue;
2162 };
2163
2164 let owner_program_id = account.owner();
2166
2167 let idl_versions = match self.registered_idls.get(&owner_program_id.to_string()) {
2169 Ok(Some(versions)) => versions,
2170 Ok(None) => {
2171 warn!(
2172 "No IDL registered for program {} (owner of account {}), skipping override {}",
2173 owner_program_id, account_pubkey, override_instance.id
2174 );
2175 continue;
2176 }
2177 Err(e) => {
2178 warn!(
2179 "Failed to get IDL for program {}: {}, skipping override {}",
2180 owner_program_id, e, override_instance.id
2181 );
2182 continue;
2183 }
2184 };
2185
2186 let Some(versioned_idl) = idl_versions.first() else {
2188 warn!(
2189 "IDL versions empty for program {}, skipping override {}",
2190 owner_program_id, override_instance.id
2191 );
2192 continue;
2193 };
2194
2195 let idl = &versioned_idl.1;
2196
2197 let account_data = account.data();
2199
2200 let new_account_data = match self.get_forged_account_data(
2202 &account_pubkey,
2203 account_data,
2204 idl,
2205 &override_instance.values,
2206 ) {
2207 Ok(data) => data,
2208 Err(e) => {
2209 warn!(
2210 "Failed to forge account data for {} (override {}): {}",
2211 account_pubkey, override_instance.id, e
2212 );
2213 continue;
2214 }
2215 };
2216
2217 let modified_account = Account {
2219 lamports: account.lamports(),
2220 data: new_account_data,
2221 owner: *account.owner(),
2222 executable: account.executable(),
2223 rent_epoch: account.rent_epoch(),
2224 };
2225
2226 if let Err(e) = self.inner.set_account(account_pubkey, modified_account) {
2228 warn!(
2229 "Failed to set modified account {} in SVM: {}",
2230 account_pubkey, e
2231 );
2232 } else {
2233 debug!(
2234 "Successfully applied {} override(s) to account {} (override {})",
2235 override_instance.values.len(),
2236 account_pubkey,
2237 override_instance.id
2238 );
2239 }
2240 }
2241 }
2242
2243 Ok(())
2244 }
2245
2246 pub fn get_forged_account_data(
2266 &self,
2267 account_pubkey: &Pubkey,
2268 account_data: &[u8],
2269 idl: &Idl,
2270 overrides: &HashMap<String, serde_json::Value>,
2271 ) -> SurfpoolResult<Vec<u8>> {
2272 if account_data.len() < 8 {
2274 return Err(SurfpoolError::invalid_account_data(
2275 account_pubkey,
2276 "Account data too small to be an Anchor account (need at least 8 bytes for discriminator)",
2277 Some("Data length too small"),
2278 ));
2279 }
2280
2281 let discriminator = &account_data[..8];
2283 let serialized_data = &account_data[8..];
2284
2285 let account_def = idl
2287 .accounts
2288 .iter()
2289 .find(|acc| acc.discriminator.eq(discriminator))
2290 .ok_or_else(|| {
2291 SurfpoolError::internal(format!(
2292 "Account with discriminator '{:?}' not found in IDL",
2293 discriminator
2294 ))
2295 })?;
2296
2297 let account_type = idl
2299 .types
2300 .iter()
2301 .find(|t| t.name == account_def.name)
2302 .ok_or_else(|| {
2303 SurfpoolError::internal(format!(
2304 "Type definition for account '{}' not found in IDL",
2305 account_def.name
2306 ))
2307 })?;
2308
2309 let empty_vec = vec![];
2311 let idl_type_def_generics = idl
2312 .types
2313 .iter()
2314 .find(|t| t.name == account_type.name)
2315 .map(|t| &t.generics);
2316
2317 let (mut parsed_value, leftover_bytes) =
2320 parse_bytes_to_value_with_expected_idl_type_def_ty_with_leftover_bytes(
2321 serialized_data,
2322 &account_type.ty,
2323 &idl.types,
2324 &vec![],
2325 idl_type_def_generics.unwrap_or(&empty_vec),
2326 )
2327 .map_err(|e| {
2328 SurfpoolError::deserialize_error(
2329 "account data",
2330 format!("Failed to deserialize account data using Borsh: {}", e),
2331 )
2332 })?;
2333
2334 for (path, value) in overrides {
2336 apply_override_to_decoded_account(&mut parsed_value, path, value)?;
2337 }
2338
2339 use anchor_lang_idl::types::{IdlGenericArg, IdlType};
2342 let defined_type = IdlType::Defined {
2343 name: account_type.name.clone(),
2344 generics: account_type
2345 .generics
2346 .iter()
2347 .map(|_| IdlGenericArg::Type {
2348 ty: IdlType::String,
2349 })
2350 .collect(),
2351 };
2352
2353 let re_encoded_data =
2355 borsh_encode_value_to_idl_type(&parsed_value, &defined_type, &idl.types, None)
2356 .map_err(|e| {
2357 SurfpoolError::internal(format!(
2358 "Failed to re-encode account data using Borsh: {}",
2359 e
2360 ))
2361 })?;
2362
2363 let mut new_account_data =
2365 Vec::with_capacity(8 + re_encoded_data.len() + leftover_bytes.len());
2366 new_account_data.extend_from_slice(discriminator);
2367 new_account_data.extend_from_slice(&re_encoded_data);
2368 new_account_data.extend_from_slice(leftover_bytes);
2369
2370 Ok(new_account_data)
2371 }
2372
2373 pub fn subscribe_for_signature_updates(
2382 &mut self,
2383 signature: &Signature,
2384 subscription_type: SignatureSubscriptionType,
2385 ) -> Receiver<(Slot, Option<TransactionError>)> {
2386 let (tx, rx) = unbounded();
2387 self.signature_subscriptions
2388 .entry(*signature)
2389 .or_default()
2390 .push((subscription_type, tx));
2391 rx
2392 }
2393
2394 pub fn subscribe_for_account_updates(
2395 &mut self,
2396 account_pubkey: &Pubkey,
2397 encoding: Option<UiAccountEncoding>,
2398 ) -> Receiver<UiAccount> {
2399 let (tx, rx) = unbounded();
2400 self.account_subscriptions
2401 .entry(*account_pubkey)
2402 .or_default()
2403 .push((encoding, tx));
2404 rx
2405 }
2406
2407 pub fn subscribe_for_program_updates(
2408 &mut self,
2409 program_id: &Pubkey,
2410 encoding: Option<UiAccountEncoding>,
2411 filters: Option<Vec<RpcFilterType>>,
2412 ) -> Receiver<RpcKeyedAccount> {
2413 let (tx, rx) = unbounded();
2414 self.program_subscriptions
2415 .entry(*program_id)
2416 .or_default()
2417 .push((encoding, filters, tx));
2418 rx
2419 }
2420
2421 pub fn notify_signature_subscribers(
2429 &mut self,
2430 status: SignatureSubscriptionType,
2431 signature: &Signature,
2432 slot: Slot,
2433 err: Option<TransactionError>,
2434 ) {
2435 let mut remaining = vec![];
2436 if let Some(subscriptions) = self.signature_subscriptions.remove(signature) {
2437 for (subscription_type, tx) in subscriptions {
2438 if status.eq(&subscription_type) {
2439 if tx.send((slot, err.clone())).is_err() {
2440 continue;
2442 }
2443 } else {
2444 remaining.push((subscription_type, tx));
2445 }
2446 }
2447 if !remaining.is_empty() {
2448 self.signature_subscriptions.insert(*signature, remaining);
2449 }
2450 }
2451 }
2452
2453 pub fn notify_account_subscribers(
2454 &mut self,
2455 account_updated_pubkey: &Pubkey,
2456 account: &Account,
2457 ) {
2458 let mut remaining = vec![];
2459 if let Some(subscriptions) = self.account_subscriptions.remove(account_updated_pubkey) {
2460 for (encoding, tx) in subscriptions {
2461 let config = RpcAccountInfoConfig {
2462 encoding,
2463 ..Default::default()
2464 };
2465 let account = self
2466 .account_to_rpc_keyed_account(account_updated_pubkey, account, &config, None)
2467 .account;
2468 if tx.send(account).is_err() {
2469 continue;
2471 } else {
2472 remaining.push((encoding, tx));
2473 }
2474 }
2475 if !remaining.is_empty() {
2476 self.account_subscriptions
2477 .insert(*account_updated_pubkey, remaining);
2478 }
2479 }
2480 }
2481
2482 pub fn notify_program_subscribers(&mut self, account_pubkey: &Pubkey, account: &Account) {
2483 let program_id = account.owner;
2484 let mut remaining = vec![];
2485 if let Some(subscriptions) = self.program_subscriptions.remove(&program_id) {
2486 for (encoding, filters, tx) in subscriptions {
2487 if let Some(ref active_filters) = filters {
2489 match super::locker::apply_rpc_filters(&account.data, active_filters) {
2490 Ok(true) => {} Ok(false) => {
2492 remaining.push((encoding, filters, tx));
2494 continue;
2495 }
2496 Err(_) => {
2497 remaining.push((encoding, filters, tx));
2499 continue;
2500 }
2501 }
2502 }
2503
2504 let config = RpcAccountInfoConfig {
2505 encoding,
2506 ..Default::default()
2507 };
2508 let keyed_account =
2509 self.account_to_rpc_keyed_account(account_pubkey, account, &config, None);
2510 if tx.send(keyed_account).is_err() {
2511 continue;
2513 } else {
2514 remaining.push((encoding, filters, tx));
2515 }
2516 }
2517 if !remaining.is_empty() {
2518 self.program_subscriptions.insert(program_id, remaining);
2519 }
2520 }
2521 }
2522
2523 pub fn get_block_at_slot(
2532 &self,
2533 slot: Slot,
2534 config: &RpcBlockConfig,
2535 ) -> SurfpoolResult<Option<UiConfirmedBlock>> {
2536 let Some(block) = self.get_block_or_reconstruct(slot)? else {
2538 return Ok(None);
2539 };
2540
2541 let show_rewards = config.rewards.unwrap_or(true);
2542 let transaction_details = config
2543 .transaction_details
2544 .unwrap_or(TransactionDetails::Full);
2545
2546 let transactions = match transaction_details {
2547 TransactionDetails::Full => Some(
2548 block
2549 .signatures
2550 .iter()
2551 .filter_map(|sig| self.transactions.get(&sig.to_string()).ok().flatten())
2552 .map(|tx_with_meta| {
2553 let (meta, _) = tx_with_meta.expect_processed();
2554 meta.encode(
2555 config.encoding.unwrap_or(
2556 solana_transaction_status::UiTransactionEncoding::JsonParsed,
2557 ),
2558 config.max_supported_transaction_version,
2559 show_rewards,
2560 )
2561 })
2562 .collect::<Result<Vec<_>, _>>()
2563 .map_err(SurfpoolError::from)?,
2564 ),
2565 TransactionDetails::Signatures => None,
2566 TransactionDetails::None => None,
2567 TransactionDetails::Accounts => Some(
2568 block
2569 .signatures
2570 .iter()
2571 .filter_map(|sig| self.transactions.get(&sig.to_string()).ok().flatten())
2572 .map(|tx_with_meta| {
2573 let (meta, _) = tx_with_meta.expect_processed();
2574 meta.to_json_accounts(
2575 config.max_supported_transaction_version,
2576 show_rewards,
2577 )
2578 })
2579 .collect::<Result<Vec<_>, _>>()
2580 .map_err(SurfpoolError::from)?,
2581 ),
2582 };
2583
2584 let signatures = match transaction_details {
2585 TransactionDetails::Signatures => {
2586 Some(block.signatures.iter().map(|t| t.to_string()).collect())
2587 }
2588 TransactionDetails::Full | TransactionDetails::Accounts | TransactionDetails::None => {
2589 None
2590 }
2591 };
2592
2593 let block = UiConfirmedBlock {
2594 previous_blockhash: block.previous_blockhash.clone(),
2595 blockhash: block.hash.clone(),
2596 parent_slot: block.parent_slot,
2597 transactions,
2598 signatures,
2599 rewards: if show_rewards { Some(vec![]) } else { None },
2600 num_reward_partitions: None,
2601 block_time: Some(block.block_time / 1000),
2602 block_height: Some(block.block_height),
2603 };
2604 Ok(Some(block))
2605 }
2606
2607 pub fn blockhash_for_slot(&self, slot: Slot) -> Option<Hash> {
2609 self.blocks
2610 .get(&slot)
2611 .unwrap()
2612 .and_then(|header| header.hash.parse().ok())
2613 }
2614
2615 pub fn get_account_owned_by(
2625 &self,
2626 program_id: &Pubkey,
2627 ) -> SurfpoolResult<Vec<(Pubkey, Account)>> {
2628 let account_pubkeys = self
2629 .accounts_by_owner
2630 .get(&program_id.to_string())
2631 .ok()
2632 .flatten()
2633 .unwrap_or_default();
2634
2635 account_pubkeys
2636 .iter()
2637 .filter_map(|pk_str| {
2638 let pk = Pubkey::from_str(pk_str).ok()?;
2639 self.get_account(&pk)
2640 .map(|res| res.map(|account| (pk, account.clone())))
2641 .transpose()
2642 })
2643 .collect::<Result<Vec<_>, SurfpoolError>>()
2644 }
2645
2646 fn get_additional_data(
2647 &self,
2648 pubkey: &Pubkey,
2649 token_mint: Option<Pubkey>,
2650 ) -> Option<AccountAdditionalDataV3> {
2651 let token_mint = if let Some(mint) = token_mint {
2652 Some(mint)
2653 } else {
2654 self.token_accounts
2655 .get(&pubkey.to_string())
2656 .ok()
2657 .flatten()
2658 .map(|ta| ta.mint())
2659 };
2660
2661 token_mint.and_then(|mint| {
2662 self.account_associated_data
2663 .get(&mint.to_string())
2664 .ok()
2665 .flatten()
2666 .and_then(|data| data.try_into().ok())
2667 })
2668 }
2669
2670 pub fn account_to_rpc_keyed_account<T: ReadableAccount>(
2671 &self,
2672 pubkey: &Pubkey,
2673 account: &T,
2674 config: &RpcAccountInfoConfig,
2675 token_mint: Option<Pubkey>,
2676 ) -> RpcKeyedAccount {
2677 let additional_data = self.get_additional_data(pubkey, token_mint);
2678
2679 RpcKeyedAccount {
2680 pubkey: pubkey.to_string(),
2681 account: self.encode_ui_account(
2682 pubkey,
2683 account,
2684 config.encoding.unwrap_or(UiAccountEncoding::Base64),
2685 additional_data,
2686 config.data_slice,
2687 ),
2688 }
2689 }
2690
2691 pub fn get_token_accounts_by_delegate(&self, delegate: &Pubkey) -> Vec<(Pubkey, TokenAccount)> {
2701 if let Some(account_pubkeys) = self
2702 .token_accounts_by_delegate
2703 .get(&delegate.to_string())
2704 .ok()
2705 .flatten()
2706 {
2707 account_pubkeys
2708 .iter()
2709 .filter_map(|pk_str| {
2710 let pk = Pubkey::from_str(pk_str).ok()?;
2711 self.token_accounts
2712 .get(pk_str)
2713 .ok()
2714 .flatten()
2715 .map(|ta| (pk, ta))
2716 })
2717 .collect()
2718 } else {
2719 Vec::new()
2720 }
2721 }
2722
2723 pub fn get_parsed_token_accounts_by_owner(
2733 &self,
2734 owner: &Pubkey,
2735 ) -> Vec<(Pubkey, TokenAccount)> {
2736 if let Some(account_pubkeys) = self
2737 .token_accounts_by_owner
2738 .get(&owner.to_string())
2739 .ok()
2740 .flatten()
2741 {
2742 account_pubkeys
2743 .iter()
2744 .filter_map(|pk_str| {
2745 let pk = Pubkey::from_str(pk_str).ok()?;
2746 self.token_accounts
2747 .get(pk_str)
2748 .ok()
2749 .flatten()
2750 .map(|ta| (pk, ta))
2751 })
2752 .collect()
2753 } else {
2754 Vec::new()
2755 }
2756 }
2757
2758 pub fn get_token_accounts_by_owner(
2759 &self,
2760 owner: &Pubkey,
2761 ) -> SurfpoolResult<Vec<(Pubkey, Account)>> {
2762 let account_pubkeys = self
2763 .token_accounts_by_owner
2764 .get(&owner.to_string())
2765 .ok()
2766 .flatten()
2767 .unwrap_or_default();
2768
2769 account_pubkeys
2770 .iter()
2771 .filter_map(|pk_str| {
2772 let pk = Pubkey::from_str(pk_str).ok()?;
2773 self.get_account(&pk)
2774 .map(|res| res.map(|account| (pk, account.clone())))
2775 .transpose()
2776 })
2777 .collect::<Result<Vec<_>, SurfpoolError>>()
2778 }
2779
2780 pub fn get_token_accounts_by_mint(&self, mint: &Pubkey) -> Vec<(Pubkey, TokenAccount)> {
2790 if let Some(account_pubkeys) = self
2791 .token_accounts_by_mint
2792 .get(&mint.to_string())
2793 .ok()
2794 .flatten()
2795 {
2796 account_pubkeys
2797 .iter()
2798 .filter_map(|pk_str| {
2799 let pk = Pubkey::from_str(pk_str).ok()?;
2800 self.token_accounts
2801 .get(pk_str)
2802 .ok()
2803 .flatten()
2804 .map(|ta| (pk, ta))
2805 })
2806 .collect()
2807 } else {
2808 Vec::new()
2809 }
2810 }
2811
2812 pub fn subscribe_for_slot_updates(&mut self) -> Receiver<SlotInfo> {
2813 let (tx, rx) = unbounded();
2814 self.slot_subscriptions.push(tx);
2815 rx
2816 }
2817
2818 pub fn notify_slot_subscribers(&mut self, slot: Slot, parent: Slot, root: Slot) {
2819 self.slot_subscriptions
2820 .retain(|tx| tx.send(SlotInfo { slot, parent, root }).is_ok());
2821 }
2822
2823 pub fn write_simulated_profile_result(
2824 &mut self,
2825 uuid: Uuid,
2826 tag: Option<String>,
2827 profile_result: KeyedProfileResult,
2828 ) -> SurfpoolResult<()> {
2829 self.simulated_transaction_profiles
2830 .store(uuid.to_string(), profile_result)?;
2831
2832 let tag = tag.unwrap_or_else(|| uuid.to_string());
2833 let mut tags = self
2834 .profile_tag_map
2835 .get(&tag)
2836 .ok()
2837 .flatten()
2838 .unwrap_or_default();
2839 tags.push(UuidOrSignature::Uuid(uuid));
2840 self.profile_tag_map.store(tag, tags)?;
2841 Ok(())
2842 }
2843
2844 pub fn write_executed_profile_result(
2845 &mut self,
2846 signature: Signature,
2847 profile_result: KeyedProfileResult,
2848 ) -> SurfpoolResult<()> {
2849 self.executed_transaction_profiles
2850 .store(signature.to_string(), profile_result)?;
2851 let tag = signature.to_string();
2852 let mut tags = self
2853 .profile_tag_map
2854 .get(&tag)
2855 .ok()
2856 .flatten()
2857 .unwrap_or_default();
2858 tags.push(UuidOrSignature::Signature(signature));
2859 self.profile_tag_map.store(tag, tags)?;
2860 Ok(())
2861 }
2862
2863 pub fn subscribe_for_logs_updates(
2864 &mut self,
2865 commitment_level: &CommitmentLevel,
2866 filter: &RpcTransactionLogsFilter,
2867 ) -> Receiver<(Slot, RpcLogsResponse)> {
2868 let (tx, rx) = unbounded();
2869 self.logs_subscriptions
2870 .push((*commitment_level, filter.clone(), tx));
2871 rx
2872 }
2873
2874 pub fn notify_logs_subscribers(
2875 &mut self,
2876 signature: &Signature,
2877 err: Option<TransactionError>,
2878 logs: Vec<String>,
2879 commitment_level: CommitmentLevel,
2880 ) {
2881 for (expected_level, filter, tx) in self.logs_subscriptions.iter() {
2882 if !expected_level.eq(&commitment_level) {
2883 continue; }
2885
2886 let should_notify = match filter {
2887 RpcTransactionLogsFilter::All | RpcTransactionLogsFilter::AllWithVotes => true,
2888
2889 RpcTransactionLogsFilter::Mentions(mentioned_accounts) => {
2890 let transaction_accounts =
2892 if let Some(SurfnetTransactionStatus::Processed(tx_data)) =
2893 self.transactions.get(&signature.to_string()).ok().flatten()
2894 {
2895 let (tx_meta, _) = tx_data.as_ref();
2896 let mut accounts = match &tx_meta.transaction.message {
2897 VersionedMessage::Legacy(msg) => msg.account_keys.clone(),
2898 VersionedMessage::V0(msg) => msg.account_keys.clone(),
2899 };
2900
2901 accounts.extend(&tx_meta.meta.loaded_addresses.writable);
2902 accounts.extend(&tx_meta.meta.loaded_addresses.readonly);
2903 Some(accounts)
2904 } else {
2905 None
2906 };
2907
2908 let Some(accounts) = transaction_accounts else {
2909 continue;
2910 };
2911
2912 mentioned_accounts.iter().any(|filtered_acc| {
2913 if let Ok(filtered_pubkey) = Pubkey::from_str(&filtered_acc) {
2914 accounts.contains(&filtered_pubkey)
2915 } else {
2916 false
2917 }
2918 })
2919 }
2920 };
2921
2922 if should_notify {
2923 let message = RpcLogsResponse {
2924 signature: signature.to_string(),
2925 err: err.clone().map(|e| e.into()),
2926 logs: logs.clone(),
2927 };
2928 let _ = tx.send((self.get_latest_absolute_slot(), message));
2929 }
2930 }
2931 }
2932
2933 pub fn register_snapshot_subscription(
2936 &mut self,
2937 ) -> (
2938 Sender<super::SnapshotImportNotification>,
2939 Receiver<super::SnapshotImportNotification>,
2940 ) {
2941 let (tx, rx) = unbounded();
2942 self.snapshot_subscriptions.push(tx.clone());
2943 (tx, rx)
2944 }
2945
2946 pub async fn fetch_snapshot_from_url(
2947 snapshot_url: &str,
2948 ) -> Result<
2949 std::collections::BTreeMap<String, Option<surfpool_types::AccountSnapshot>>,
2950 Box<dyn std::error::Error + Send + Sync>,
2951 > {
2952 let response = reqwest::get(snapshot_url).await?;
2953 let text = response.text().await?;
2954
2955 let snapshot: std::collections::BTreeMap<String, Option<surfpool_types::AccountSnapshot>> =
2957 serde_json::from_str(&text)?;
2958
2959 Ok(snapshot)
2960 }
2961
2962 pub fn register_idl(&mut self, idl: Idl, slot: Option<Slot>) -> SurfpoolResult<()> {
2963 let slot = slot.unwrap_or(self.latest_epoch_info.absolute_slot);
2964 let program_id = Pubkey::from_str_const(&idl.address);
2965 let program_id_str = program_id.to_string();
2966 let mut idl_versions = self
2967 .registered_idls
2968 .get(&program_id_str)
2969 .ok()
2970 .flatten()
2971 .unwrap_or_default();
2972 idl_versions.push(VersionedIdl(slot, idl));
2973 idl_versions.sort_by(|a, b| b.0.cmp(&a.0));
2975 self.registered_idls.store(program_id_str, idl_versions)?;
2976 Ok(())
2977 }
2978
2979 fn encode_ui_account_profile_state(
2980 &self,
2981 pubkey: &Pubkey,
2982 account_profile_state: AccountProfileState,
2983 encoding: &UiAccountEncoding,
2984 ) -> UiAccountProfileState {
2985 let additional_data = self.get_additional_data(pubkey, None);
2986
2987 match account_profile_state {
2988 AccountProfileState::Readonly => UiAccountProfileState::Readonly,
2989 AccountProfileState::Writable(account_change) => {
2990 let change = match account_change {
2991 AccountChange::Create(account) => UiAccountChange::Create(
2992 self.encode_ui_account(pubkey, &account, *encoding, additional_data, None),
2993 ),
2994 AccountChange::Update(account_before, account_after) => {
2995 UiAccountChange::Update(
2996 self.encode_ui_account(
2997 pubkey,
2998 &account_before,
2999 *encoding,
3000 additional_data,
3001 None,
3002 ),
3003 self.encode_ui_account(
3004 pubkey,
3005 &account_after,
3006 *encoding,
3007 additional_data,
3008 None,
3009 ),
3010 )
3011 }
3012 AccountChange::Delete(account) => UiAccountChange::Delete(
3013 self.encode_ui_account(pubkey, &account, *encoding, additional_data, None),
3014 ),
3015 AccountChange::Unchanged(account) => {
3016 UiAccountChange::Unchanged(account.map(|account| {
3017 self.encode_ui_account(
3018 pubkey,
3019 &account,
3020 *encoding,
3021 additional_data,
3022 None,
3023 )
3024 }))
3025 }
3026 };
3027 UiAccountProfileState::Writable(change)
3028 }
3029 }
3030 }
3031
3032 fn encode_ui_profile_result(
3033 &self,
3034 profile_result: ProfileResult,
3035 readonly_accounts: &[Pubkey],
3036 encoding: &UiAccountEncoding,
3037 ) -> UiProfileResult {
3038 let ProfileResult {
3039 pre_execution_capture,
3040 post_execution_capture,
3041 compute_units_consumed,
3042 log_messages,
3043 error_message,
3044 } = profile_result;
3045
3046 let account_states = pre_execution_capture
3047 .into_iter()
3048 .zip(post_execution_capture)
3049 .map(|((pubkey, pre_account), (_, post_account))| {
3050 let state =
3057 AccountProfileState::new(pubkey, pre_account, post_account, readonly_accounts);
3058 (
3059 pubkey,
3060 self.encode_ui_account_profile_state(&pubkey, state, encoding),
3061 )
3062 })
3063 .collect::<IndexMap<Pubkey, UiAccountProfileState>>();
3064
3065 UiProfileResult {
3066 account_states,
3067 compute_units_consumed,
3068 log_messages,
3069 error_message,
3070 }
3071 }
3072
3073 pub fn encode_ui_keyed_profile_result(
3074 &self,
3075 keyed_profile_result: KeyedProfileResult,
3076 config: &RpcProfileResultConfig,
3077 ) -> UiKeyedProfileResult {
3078 let KeyedProfileResult {
3079 slot,
3080 key,
3081 instruction_profiles,
3082 transaction_profile,
3083 readonly_account_states,
3084 } = keyed_profile_result;
3085
3086 let encoding = config.encoding.unwrap_or(UiAccountEncoding::JsonParsed);
3087
3088 let readonly_accounts = readonly_account_states.keys().cloned().collect::<Vec<_>>();
3089
3090 let default = RpcProfileDepth::default();
3091 let instruction_profiles = match *config.depth.as_ref().unwrap_or(&default) {
3092 RpcProfileDepth::Transaction => None,
3093 RpcProfileDepth::Instruction => instruction_profiles.map(|instruction_profiles| {
3094 instruction_profiles
3095 .into_iter()
3096 .map(|p| self.encode_ui_profile_result(p, &readonly_accounts, &encoding))
3097 .collect()
3098 }),
3099 };
3100
3101 let transaction_profile =
3102 self.encode_ui_profile_result(transaction_profile, &readonly_accounts, &encoding);
3103
3104 let readonly_account_states = readonly_account_states
3105 .into_iter()
3106 .map(|(pubkey, account)| {
3107 let account = self.encode_ui_account(&pubkey, &account, encoding, None, None);
3108 (pubkey, account)
3109 })
3110 .collect();
3111
3112 UiKeyedProfileResult {
3113 slot,
3114 key,
3115 instruction_profiles,
3116 transaction_profile,
3117 readonly_account_states,
3118 }
3119 }
3120
3121 pub fn encode_ui_account<T: ReadableAccount>(
3122 &self,
3123 pubkey: &Pubkey,
3124 account: &T,
3125 encoding: UiAccountEncoding,
3126 additional_data: Option<AccountAdditionalDataV3>,
3127 data_slice_config: Option<UiDataSliceConfig>,
3128 ) -> UiAccount {
3129 let owner_program_id = account.owner();
3130 let data = account.data();
3131
3132 if encoding == UiAccountEncoding::JsonParsed {
3133 if let Ok(Some(registered_idls)) =
3134 self.registered_idls.get(&owner_program_id.to_string())
3135 {
3136 let filter_slot = self.latest_epoch_info.absolute_slot;
3137 let ordered_available_idls = registered_idls
3139 .iter()
3140 .filter_map(|VersionedIdl(slot, idl)| {
3142 if *slot <= filter_slot {
3143 Some(idl)
3144 } else {
3145 None
3146 }
3147 })
3148 .collect::<Vec<_>>();
3149
3150 for idl in &ordered_available_idls {
3154 let discriminator = &data[..8];
3156 if let Some(matching_account) = idl
3157 .accounts
3158 .iter()
3159 .find(|a| a.discriminator.eq(&discriminator))
3160 {
3161 if let Some(account_type) =
3163 idl.types.iter().find(|t| t.name == matching_account.name)
3164 {
3165 let empty_vec = vec![];
3166 let idl_type_def_generics = idl
3167 .types
3168 .iter()
3169 .find(|t| t.name == account_type.name)
3170 .map(|t| &t.generics);
3171
3172 let rest = data[8..].as_ref();
3174 if let Ok(parsed_value) =
3175 parse_bytes_to_value_with_expected_idl_type_def_ty(
3176 rest,
3177 &account_type.ty,
3178 &idl.types,
3179 &vec![],
3180 idl_type_def_generics.unwrap_or(&empty_vec),
3181 )
3182 {
3183 return UiAccount {
3184 lamports: account.lamports(),
3185 data: UiAccountData::Json(ParsedAccount {
3186 program: idl
3187 .metadata
3188 .name
3189 .to_string()
3190 .to_case(convert_case::Case::Kebab),
3191 parsed: parsed_value
3192 .to_json(Some(&get_txtx_value_json_converters())),
3193 space: data.len() as u64,
3194 }),
3195 owner: owner_program_id.to_string(),
3196 executable: account.executable(),
3197 rent_epoch: account.rent_epoch(),
3198 space: Some(data.len() as u64),
3199 };
3200 }
3201 }
3202 }
3203 }
3204 }
3205 }
3206
3207 encode_ui_account(
3209 pubkey,
3210 account,
3211 encoding,
3212 additional_data,
3213 data_slice_config,
3214 )
3215 }
3216
3217 pub fn get_account(&self, pubkey: &Pubkey) -> SurfpoolResult<Option<Account>> {
3218 self.inner.get_account(pubkey)
3219 }
3220
3221 pub fn get_all_accounts(&self) -> SurfpoolResult<Vec<(Pubkey, AccountSharedData)>> {
3222 self.inner.get_all_accounts()
3223 }
3224
3225 pub fn get_transaction(
3226 &self,
3227 signature: &Signature,
3228 ) -> SurfpoolResult<Option<SurfnetTransactionStatus>> {
3229 Ok(self.transactions.get(&signature.to_string())?)
3230 }
3231
3232 pub fn start_runbook_execution(&mut self, runbook_id: String) {
3233 self.runbook_executions
3234 .push(RunbookExecutionStatusReport::new(runbook_id));
3235 }
3236
3237 pub fn complete_runbook_execution(&mut self, runbook_id: &str, error: Option<Vec<String>>) {
3238 if let Some(execution) = self
3239 .runbook_executions
3240 .iter_mut()
3241 .find(|e| e.runbook_id.eq(runbook_id) && e.completed_at.is_none())
3242 {
3243 execution.mark_completed(error);
3244 }
3245 }
3246
3247 pub fn export_snapshot(
3255 &self,
3256 config: ExportSnapshotConfig,
3257 ) -> SurfpoolResult<BTreeMap<String, AccountSnapshot>> {
3258 let mut fixtures = BTreeMap::new();
3259 let encoding = if config.include_parsed_accounts.unwrap_or_default() {
3260 UiAccountEncoding::JsonParsed
3261 } else {
3262 UiAccountEncoding::Base64
3263 };
3264 let filter = config.filter.unwrap_or_default();
3265 let include_program_accounts = filter.include_program_accounts.unwrap_or(false);
3266 let include_accounts = filter.include_accounts.unwrap_or_default();
3267 let exclude_accounts = filter.exclude_accounts.unwrap_or_default();
3268
3269 fn is_program_account(pubkey: &Pubkey) -> bool {
3270 pubkey == &bpf_loader::id()
3271 || pubkey == &solana_sdk_ids::bpf_loader_deprecated::id()
3272 || pubkey == &solana_sdk_ids::bpf_loader_upgradeable::id()
3273 }
3274
3275 let mut process_account = |pubkey: &Pubkey, account: &Account| {
3277 let is_include_account = include_accounts.iter().any(|k| k.eq(&pubkey.to_string()));
3278 let is_exclude_account = exclude_accounts.iter().any(|k| k.eq(&pubkey.to_string()));
3279 let is_program_account = is_program_account(&account.owner);
3280 if is_exclude_account
3281 || ((is_program_account && !include_program_accounts) && !is_include_account)
3282 {
3283 return;
3284 }
3285
3286 let additional_data: Option<AccountAdditionalDataV3> = if account.owner
3288 == spl_token_interface::id()
3289 || account.owner == spl_token_2022_interface::id()
3290 {
3291 if let Ok(token_account) = TokenAccount::unpack(&account.data) {
3292 self.account_associated_data
3293 .get(&token_account.mint().to_string())
3294 .ok()
3295 .flatten()
3296 .and_then(|data| data.try_into().ok())
3297 } else {
3298 self.account_associated_data
3299 .get(&pubkey.to_string())
3300 .ok()
3301 .flatten()
3302 .and_then(|data| data.try_into().ok())
3303 }
3304 } else {
3305 self.account_associated_data
3306 .get(&pubkey.to_string())
3307 .ok()
3308 .flatten()
3309 .and_then(|data| data.try_into().ok())
3310 };
3311
3312 let ui_account =
3313 self.encode_ui_account(pubkey, account, encoding, additional_data, None);
3314
3315 let (base64, parsed_data) = match ui_account.data {
3316 UiAccountData::Json(parsed_account) => {
3317 (BASE64_STANDARD.encode(account.data()), Some(parsed_account))
3318 }
3319 UiAccountData::Binary(base64, _) => (base64, None),
3320 UiAccountData::LegacyBinary(_) => unreachable!(),
3321 };
3322
3323 let account_snapshot = AccountSnapshot::new(
3324 account.lamports,
3325 account.owner.to_string(),
3326 account.executable,
3327 account.rent_epoch,
3328 base64,
3329 parsed_data,
3330 );
3331
3332 fixtures.insert(pubkey.to_string(), account_snapshot);
3333 };
3334
3335 match &config.scope {
3336 ExportSnapshotScope::Network => {
3337 for (pubkey, account_shared_data) in self.get_all_accounts()? {
3339 let account = Account::from(account_shared_data.clone());
3340 process_account(&pubkey, &account);
3341 }
3342 }
3343 ExportSnapshotScope::PreTransaction(signature_str) => {
3344 if let Ok(signature) = Signature::from_str(signature_str) {
3346 if let Ok(Some(profile)) = self
3347 .executed_transaction_profiles
3348 .get(&signature.to_string())
3349 {
3350 for (pubkey, account_opt) in
3353 &profile.transaction_profile.pre_execution_capture
3354 {
3355 if let Some(account) = account_opt {
3356 process_account(pubkey, account);
3357 }
3358 }
3359
3360 for (pubkey, account) in &profile.readonly_account_states {
3362 process_account(pubkey, account);
3363 }
3364 }
3365 }
3366 }
3367 }
3368
3369 Ok(fixtures)
3370 }
3371
3372 pub fn register_scenario(
3377 &mut self,
3378 scenario: surfpool_types::Scenario,
3379 slot: Option<Slot>,
3380 ) -> SurfpoolResult<()> {
3381 let base_slot = slot.unwrap_or(self.latest_epoch_info.absolute_slot);
3383
3384 info!(
3385 "Registering scenario: {} ({}) with {} overrides at base slot {}",
3386 scenario.name,
3387 scenario.id,
3388 scenario.overrides.len(),
3389 base_slot
3390 );
3391
3392 for override_instance in scenario.overrides {
3394 let scenario_relative_slot = override_instance.scenario_relative_slot;
3395 let absolute_slot = base_slot + scenario_relative_slot;
3396
3397 debug!(
3398 "Scheduling override at absolute slot {} (base {} + relative {})",
3399 absolute_slot, base_slot, scenario_relative_slot
3400 );
3401
3402 let mut slot_overrides = self
3403 .scheduled_overrides
3404 .get(&absolute_slot)
3405 .ok()
3406 .flatten()
3407 .unwrap_or_default();
3408 slot_overrides.push(override_instance);
3409 self.scheduled_overrides
3410 .store(absolute_slot, slot_overrides)?;
3411 }
3412
3413 Ok(())
3414 }
3415}
3416
3417#[cfg(test)]
3418mod tests {
3419 use agave_feature_set::{
3420 blake3_syscall_enabled, curve25519_syscall_enabled, disable_fees_sysvar,
3421 enable_extend_program_checked, enable_loader_v4, enable_sbpf_v1_deployment_and_execution,
3422 enable_sbpf_v2_deployment_and_execution, enable_sbpf_v3_deployment_and_execution,
3423 formalize_loaded_transaction_data_size, move_precompile_verification_to_svm,
3424 raise_cpi_nesting_limit_to_8,
3425 };
3426 use base64::{Engine, engine::general_purpose};
3427 use borsh::BorshSerialize;
3428 use solana_account::Account;
3430 use solana_loader_v3_interface::get_program_data_address;
3431 use solana_program_pack::Pack;
3432 use spl_token_interface::state::{Account as TokenAccount, AccountState};
3433 use test_case::test_case;
3434
3435 use super::*;
3436 use crate::storage::tests::TestType;
3437
3438 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
3439 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
3440 #[test_case(TestType::no_db(); "with no db")]
3441 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
3442 fn test_synthetic_blockhash_generation(test_type: TestType) {
3443 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
3444
3445 let test_cases = vec![0, 1, 42, 255, 1000, 0x12345678];
3447
3448 for index in test_cases {
3449 svm.chain_tip = BlockIdentifier::new(index, "test_hash");
3450
3451 let new_blockhash = svm.new_blockhash();
3453
3454 let blockhash_str = new_blockhash.hash.clone();
3456 println!("Index {} -> Blockhash: {}", index, blockhash_str);
3457
3458 assert!(!blockhash_str.is_empty());
3460 assert!(blockhash_str.len() > 20); svm.chain_tip = BlockIdentifier::new(index, "test_hash");
3464 let new_blockhash2 = svm.new_blockhash();
3465 assert_eq!(new_blockhash.hash, new_blockhash2.hash);
3466 }
3467 }
3468
3469 #[test]
3470 fn test_synthetic_blockhash_base58_encoding() {
3471 let test_index = 42u64;
3473 let index_hex = format!("{:08x}", test_index)
3474 .replace('0', "x")
3475 .replace('O', "x");
3476
3477 let target_length = 43;
3478 let padding_needed = target_length - SyntheticBlockhash::PREFIX.len() - index_hex.len();
3479 let padding = "x".repeat(padding_needed.max(0));
3480 let target_string = format!("{}{}{}", SyntheticBlockhash::PREFIX, padding, index_hex);
3481
3482 println!("Target string: {}", target_string);
3483
3484 let decoded_bytes = bs58::decode(&target_string).into_vec();
3486 assert!(decoded_bytes.is_ok(), "String should be valid base58");
3487
3488 let bytes = decoded_bytes.unwrap();
3489 assert!(bytes.len() <= 32, "Decoded bytes should fit in 32 bytes");
3490
3491 let mut blockhash_bytes = [0u8; 32];
3493 blockhash_bytes[..bytes.len().min(32)].copy_from_slice(&bytes[..bytes.len().min(32)]);
3494 let hash = Hash::new_from_array(blockhash_bytes);
3495
3496 let hash_str = hash.to_string();
3498 assert!(!hash_str.is_empty());
3499 println!("Generated hash: {}", hash_str);
3500 }
3501
3502 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
3503 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
3504 #[test_case(TestType::no_db(); "with no db")]
3505 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
3506 fn test_blockhash_consistency_across_calls(test_type: TestType) {
3507 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
3508
3509 svm.chain_tip = BlockIdentifier::new(123, "initial_hash");
3511
3512 let mut previous_hash: Option<BlockIdentifier> = None;
3514 for i in 0..5 {
3515 let new_blockhash = svm.new_blockhash();
3516 println!(
3517 "Call {}: index={}, hash={}",
3518 i, new_blockhash.index, new_blockhash.hash
3519 );
3520
3521 if let Some(prev) = previous_hash {
3522 assert_eq!(new_blockhash.index, prev.index + 1);
3524 assert_ne!(new_blockhash.hash, prev.hash);
3526 } else {
3527 assert_eq!(new_blockhash.index, svm.chain_tip.index + 1);
3529 }
3530
3531 previous_hash = Some(new_blockhash.clone());
3532 svm.chain_tip = new_blockhash;
3534 }
3535 }
3536
3537 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
3538 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
3539 #[test_case(TestType::no_db(); "with no db")]
3540 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
3541 fn test_token_account_indexing(test_type: TestType) {
3542 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
3543
3544 let owner = Pubkey::new_unique();
3545 let delegate = Pubkey::new_unique();
3546 let mint = Pubkey::new_unique();
3547 let token_account_pubkey = Pubkey::new_unique();
3548
3549 let mut token_account_data = [0u8; TokenAccount::LEN];
3551 let token_account = TokenAccount {
3552 mint,
3553 owner,
3554 amount: 1000,
3555 delegate: COption::Some(delegate),
3556 state: AccountState::Initialized,
3557 is_native: COption::None,
3558 delegated_amount: 500,
3559 close_authority: COption::None,
3560 };
3561 token_account.pack_into_slice(&mut token_account_data);
3562
3563 let account = Account {
3564 lamports: 1000000,
3565 data: token_account_data.to_vec(),
3566 owner: spl_token_interface::id(),
3567 executable: false,
3568 rent_epoch: 0,
3569 };
3570
3571 svm.set_account(&token_account_pubkey, account).unwrap();
3572
3573 assert_eq!(svm.token_accounts.keys().unwrap().len(), 1);
3575
3576 let owner_accounts = svm.get_parsed_token_accounts_by_owner(&owner);
3578 assert_eq!(owner_accounts.len(), 1);
3579 assert_eq!(owner_accounts[0].0, token_account_pubkey);
3580
3581 let delegate_accounts = svm.get_token_accounts_by_delegate(&delegate);
3583 assert_eq!(delegate_accounts.len(), 1);
3584 assert_eq!(delegate_accounts[0].0, token_account_pubkey);
3585
3586 let mint_accounts = svm.get_token_accounts_by_mint(&mint);
3588 assert_eq!(mint_accounts.len(), 1);
3589 assert_eq!(mint_accounts[0].0, token_account_pubkey);
3590 }
3591
3592 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
3593 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
3594 #[test_case(TestType::no_db(); "with no db")]
3595 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
3596 fn test_account_update_removes_old_indexes(test_type: TestType) {
3597 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
3598
3599 let owner = Pubkey::new_unique();
3600 let old_delegate = Pubkey::new_unique();
3601 let new_delegate = Pubkey::new_unique();
3602 let mint = Pubkey::new_unique();
3603 let token_account_pubkey = Pubkey::new_unique();
3604
3605 let mut token_account_data = [0u8; TokenAccount::LEN];
3607 let token_account = TokenAccount {
3608 mint,
3609 owner,
3610 amount: 1000,
3611 delegate: COption::Some(old_delegate),
3612 state: AccountState::Initialized,
3613 is_native: COption::None,
3614 delegated_amount: 500,
3615 close_authority: COption::None,
3616 };
3617 token_account.pack_into_slice(&mut token_account_data);
3618
3619 let account = Account {
3620 lamports: 1000000,
3621 data: token_account_data.to_vec(),
3622 owner: spl_token_interface::id(),
3623 executable: false,
3624 rent_epoch: 0,
3625 };
3626
3627 svm.set_account(&token_account_pubkey, account).unwrap();
3629
3630 assert_eq!(svm.get_token_accounts_by_delegate(&old_delegate).len(), 1);
3632 assert_eq!(svm.get_token_accounts_by_delegate(&new_delegate).len(), 0);
3633
3634 let updated_token_account = TokenAccount {
3636 mint,
3637 owner,
3638 amount: 1000,
3639 delegate: COption::Some(new_delegate),
3640 state: AccountState::Initialized,
3641 is_native: COption::None,
3642 delegated_amount: 500,
3643 close_authority: COption::None,
3644 };
3645 updated_token_account.pack_into_slice(&mut token_account_data);
3646
3647 let updated_account = Account {
3648 lamports: 1000000,
3649 data: token_account_data.to_vec(),
3650 owner: spl_token_interface::id(),
3651 executable: false,
3652 rent_epoch: 0,
3653 };
3654
3655 svm.set_account(&token_account_pubkey, updated_account)
3657 .unwrap();
3658
3659 assert_eq!(svm.get_token_accounts_by_delegate(&old_delegate).len(), 0);
3661 assert_eq!(svm.get_token_accounts_by_delegate(&new_delegate).len(), 1);
3662 assert_eq!(svm.get_parsed_token_accounts_by_owner(&owner).len(), 1);
3663 }
3664
3665 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
3666 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
3667 #[test_case(TestType::no_db(); "with no db")]
3668 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
3669 fn test_non_token_accounts_not_indexed(test_type: TestType) {
3670 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
3671
3672 let system_account_pubkey = Pubkey::new_unique();
3673 let account = Account {
3674 lamports: 1000000,
3675 data: vec![],
3676 owner: solana_system_interface::program::id(), executable: false,
3678 rent_epoch: 0,
3679 };
3680
3681 svm.set_account(&system_account_pubkey, account).unwrap();
3682
3683 assert_eq!(svm.token_accounts.keys().unwrap().len(), 0);
3685 assert_eq!(svm.token_accounts_by_owner.keys().unwrap().len(), 0);
3686 assert_eq!(svm.token_accounts_by_delegate.keys().unwrap().len(), 0);
3687 assert_eq!(svm.token_accounts_by_mint.keys().unwrap().len(), 0);
3688 }
3689
3690 fn expect_account_update_event(
3691 events_rx: &Receiver<SimnetEvent>,
3692 svm: &SurfnetSvm,
3693 pubkey: &Pubkey,
3694 expected_account: &Account,
3695 ) -> bool {
3696 match events_rx.recv() {
3697 Ok(event) => match event {
3698 SimnetEvent::AccountUpdate(_, account_pubkey) => {
3699 assert_eq!(pubkey, &account_pubkey);
3700 assert_eq!(
3701 svm.get_account(&pubkey).unwrap().as_ref(),
3702 Some(expected_account)
3703 );
3704 true
3705 }
3706 event => {
3707 println!("unexpected simnet event: {:?}", event);
3708 false
3709 }
3710 },
3711 Err(_) => false,
3712 }
3713 }
3714
3715 fn _expect_error_event(events_rx: &Receiver<SimnetEvent>, expected_error: &str) -> bool {
3716 match events_rx.recv() {
3717 Ok(event) => match event {
3718 SimnetEvent::ErrorLog(_, err) => {
3719 assert_eq!(err, expected_error);
3720
3721 true
3722 }
3723 event => {
3724 println!("unexpected simnet event: {:?}", event);
3725 false
3726 }
3727 },
3728 Err(_) => false,
3729 }
3730 }
3731
3732 fn create_program_accounts() -> (Pubkey, Account, Pubkey, Account) {
3733 let program_pubkey = Pubkey::new_unique();
3734 let program_data_address = get_program_data_address(&program_pubkey);
3735 let program_account = Account {
3736 lamports: 1000000000000,
3737 data: bincode::serialize(
3738 &solana_loader_v3_interface::state::UpgradeableLoaderState::Program {
3739 programdata_address: program_data_address,
3740 },
3741 )
3742 .unwrap(),
3743 owner: solana_sdk_ids::bpf_loader_upgradeable::ID,
3744 executable: true,
3745 rent_epoch: 10000000000000,
3746 };
3747
3748 let mut bin = include_bytes!("../tests/assets/metaplex_program.bin").to_vec();
3749 let mut data = bincode::serialize(
3750 &solana_loader_v3_interface::state::UpgradeableLoaderState::ProgramData {
3751 slot: 0,
3752 upgrade_authority_address: Some(Pubkey::new_unique()),
3753 },
3754 )
3755 .unwrap();
3756 data.append(&mut bin); let program_data_account = Account {
3758 lamports: 10000000000000,
3759 data,
3760 owner: solana_sdk_ids::bpf_loader_upgradeable::ID,
3761 executable: false,
3762 rent_epoch: 10000000000000,
3763 };
3764 (
3765 program_pubkey,
3766 program_account,
3767 program_data_address,
3768 program_data_account,
3769 )
3770 }
3771
3772 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
3773 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
3774 #[test_case(TestType::no_db(); "with no db")]
3775 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
3776 fn test_inserting_account_updates(test_type: TestType) {
3777 let (mut svm, events_rx, _geyser_rx) = test_type.initialize_svm();
3778
3779 let pubkey = Pubkey::new_unique();
3780 let account = Account {
3781 lamports: 1000,
3782 data: vec![1, 2, 3],
3783 owner: Pubkey::new_unique(),
3784 executable: false,
3785 rent_epoch: 0,
3786 };
3787
3788 {
3790 let index_before = svm.get_all_accounts().unwrap();
3791 let empty_update = GetAccountResult::None(pubkey);
3792 svm.write_account_update(empty_update);
3793 assert_eq!(svm.get_all_accounts().unwrap(), index_before);
3794 }
3795
3796 {
3798 let index_before = svm.get_all_accounts().unwrap();
3799 let found_update = GetAccountResult::FoundAccount(pubkey, account.clone(), false);
3800 svm.write_account_update(found_update);
3801 assert_eq!(svm.get_all_accounts().unwrap(), index_before);
3802 }
3803
3804 {
3806 let index_before = svm.get_all_accounts().unwrap();
3807 let found_update = GetAccountResult::FoundAccount(pubkey, account.clone(), true);
3808 svm.write_account_update(found_update);
3809 assert_eq!(
3810 svm.get_all_accounts().unwrap().len(),
3811 index_before.len() + 1
3812 );
3813 if !expect_account_update_event(&events_rx, &svm, &pubkey, &account) {
3814 panic!(
3815 "Expected account update event not received after GetAccountResult::FoundAccount update"
3816 );
3817 }
3818 }
3819
3820 {
3822 let (program_address, program_account, program_data_address, _) =
3823 create_program_accounts();
3824
3825 let mut data = bincode::serialize(
3826 &solana_loader_v3_interface::state::UpgradeableLoaderState::ProgramData {
3827 slot: svm.get_latest_absolute_slot(),
3828 upgrade_authority_address: Some(system_program::id()),
3829 },
3830 )
3831 .unwrap();
3832
3833 let mut bin = include_bytes!("../tests/assets/minimum_program.so").to_vec();
3834 data.append(&mut bin); let lamports = svm.inner.minimum_balance_for_rent_exemption(data.len());
3836 let default_program_data_account = Account {
3837 lamports,
3838 data,
3839 owner: solana_sdk_ids::bpf_loader_upgradeable::ID,
3840 executable: false,
3841 rent_epoch: 0,
3842 };
3843
3844 let index_before = svm.get_all_accounts().unwrap();
3845 let found_program_account_update = GetAccountResult::FoundProgramAccount(
3846 (program_address, program_account.clone()),
3847 (program_data_address, None),
3848 );
3849 svm.write_account_update(found_program_account_update);
3850
3851 if !expect_account_update_event(
3852 &events_rx,
3853 &svm,
3854 &program_data_address,
3855 &default_program_data_account,
3856 ) {
3857 panic!(
3858 "Expected account update event not received after inserting default program data account"
3859 );
3860 }
3861
3862 if !expect_account_update_event(&events_rx, &svm, &program_address, &program_account) {
3863 panic!(
3864 "Expected account update event not received after GetAccountResult::FoundProgramAccount update for program pubkey"
3865 );
3866 }
3867 assert_eq!(
3868 svm.get_all_accounts().unwrap().len(),
3869 index_before.len() + 2
3870 );
3871 }
3872
3873 {
3875 let (program_address, program_account, program_data_address, program_data_account) =
3876 create_program_accounts();
3877
3878 let index_before = svm.get_all_accounts().unwrap();
3879 let found_program_account_update = GetAccountResult::FoundProgramAccount(
3880 (program_address, program_account.clone()),
3881 (program_data_address, Some(program_data_account.clone())),
3882 );
3883 svm.write_account_update(found_program_account_update);
3884 assert_eq!(
3885 svm.get_all_accounts().unwrap().len(),
3886 index_before.len() + 2
3887 );
3888 if !expect_account_update_event(
3889 &events_rx,
3890 &svm,
3891 &program_data_address,
3892 &program_data_account,
3893 ) {
3894 panic!(
3895 "Expected account update event not received after GetAccountResult::FoundProgramAccount update for program data pubkey"
3896 );
3897 }
3898
3899 if !expect_account_update_event(&events_rx, &svm, &program_address, &program_account) {
3900 panic!(
3901 "Expected account update event not received after GetAccountResult::FoundProgramAccount update for program pubkey"
3902 );
3903 }
3904 }
3905
3906 {
3909 let (program_address, program_account, program_data_address, program_data_account) =
3910 create_program_accounts();
3911
3912 let index_before = svm.get_all_accounts().unwrap();
3913 let found_update = GetAccountResult::FoundAccount(
3914 program_data_address,
3915 program_data_account.clone(),
3916 true,
3917 );
3918 svm.write_account_update(found_update);
3919 assert_eq!(
3920 svm.get_all_accounts().unwrap().len(),
3921 index_before.len() + 1
3922 );
3923 if !expect_account_update_event(
3924 &events_rx,
3925 &svm,
3926 &program_data_address,
3927 &program_data_account,
3928 ) {
3929 panic!(
3930 "Expected account update event not received after GetAccountResult::FoundAccount update"
3931 );
3932 }
3933
3934 let index_before = svm.get_all_accounts().unwrap();
3935 let program_account_found_update = GetAccountResult::FoundProgramAccount(
3936 (program_address, program_account.clone()),
3937 (program_data_address, None),
3938 );
3939 svm.write_account_update(program_account_found_update);
3940 assert_eq!(
3941 svm.get_all_accounts().unwrap().len(),
3942 index_before.len() + 1
3943 );
3944 if !expect_account_update_event(&events_rx, &svm, &program_address, &program_account) {
3945 panic!(
3946 "Expected account update event not received after GetAccountResult::FoundAccount update"
3947 );
3948 }
3949 }
3950 }
3951
3952 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
3953 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
3954 #[test_case(TestType::no_db(); "with no db")]
3955 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
3956 fn test_encode_ui_account(test_type: TestType) {
3957 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
3958
3959 let idl_v1: Idl =
3960 serde_json::from_slice(&include_bytes!("../tests/assets/idl_v1.json").to_vec())
3961 .unwrap();
3962
3963 svm.register_idl(idl_v1.clone(), Some(0)).unwrap();
3964
3965 let account_pubkey = Pubkey::new_unique();
3966
3967 #[derive(borsh::BorshSerialize)]
3968 pub struct CustomAccount {
3969 pub my_custom_data: u64,
3970 pub another_field: String,
3971 pub bool: bool,
3972 pub pubkey: Pubkey,
3973 }
3974
3975 {
3977 let account_data = vec![0; 100];
3978 let base64_data = general_purpose::STANDARD.encode(&account_data);
3979 let expected_data = UiAccountData::Binary(base64_data, UiAccountEncoding::Base64);
3980 let account = Account {
3981 lamports: 1000,
3982 data: account_data,
3983 owner: idl_v1.address.parse().unwrap(),
3984 executable: false,
3985 rent_epoch: 0,
3986 };
3987
3988 let ui_account = svm.encode_ui_account(
3989 &account_pubkey,
3990 &account,
3991 UiAccountEncoding::JsonParsed,
3992 None,
3993 None,
3994 );
3995 let expected_account = UiAccount {
3996 lamports: 1000,
3997 data: expected_data,
3998 owner: idl_v1.address.clone(),
3999 executable: false,
4000 rent_epoch: 0,
4001 space: Some(account.data.len() as u64),
4002 };
4003 assert_eq!(ui_account, expected_account);
4004 }
4005
4006 {
4008 let mut account_data = idl_v1.accounts[0].discriminator.clone();
4009 let pubkey = Pubkey::new_unique();
4010 CustomAccount {
4011 my_custom_data: 42,
4012 another_field: "test".to_string(),
4013 bool: true,
4014 pubkey,
4015 }
4016 .serialize(&mut account_data)
4017 .unwrap();
4018
4019 let account = Account {
4020 lamports: 1000,
4021 data: account_data,
4022 owner: idl_v1.address.parse().unwrap(),
4023 executable: false,
4024 rent_epoch: 0,
4025 };
4026
4027 let ui_account = svm.encode_ui_account(
4028 &account_pubkey,
4029 &account,
4030 UiAccountEncoding::JsonParsed,
4031 None,
4032 None,
4033 );
4034 let expected_account = UiAccount {
4035 lamports: 1000,
4036 data: UiAccountData::Json(ParsedAccount {
4037 program: format!("{}", idl_v1.metadata.name).to_case(convert_case::Case::Kebab),
4038 parsed: serde_json::json!({
4039 "my_custom_data": 42,
4040 "another_field": "test",
4041 "bool": true,
4042 "pubkey": pubkey.to_string(),
4043 }),
4044 space: account.data.len() as u64,
4045 }),
4046 owner: idl_v1.address.clone(),
4047 executable: false,
4048 rent_epoch: 0,
4049 space: Some(account.data.len() as u64),
4050 };
4051 assert_eq!(ui_account, expected_account);
4052 }
4053
4054 let idl_v2: Idl =
4055 serde_json::from_slice(&include_bytes!("../tests/assets/idl_v2.json").to_vec())
4056 .unwrap();
4057
4058 svm.register_idl(idl_v2.clone(), Some(100)).unwrap();
4059
4060 {
4062 let mut account_data = idl_v1.accounts[0].discriminator.clone();
4063 let pubkey = Pubkey::new_unique();
4064 CustomAccount {
4065 my_custom_data: 42,
4066 another_field: "test".to_string(),
4067 bool: true,
4068 pubkey,
4069 }
4070 .serialize(&mut account_data)
4071 .unwrap();
4072
4073 let account = Account {
4074 lamports: 1000,
4075 data: account_data,
4076 owner: idl_v1.address.parse().unwrap(),
4077 executable: false,
4078 rent_epoch: 0,
4079 };
4080
4081 let ui_account = svm.encode_ui_account(
4082 &account_pubkey,
4083 &account,
4084 UiAccountEncoding::JsonParsed,
4085 None,
4086 None,
4087 );
4088 let expected_account = UiAccount {
4089 lamports: 1000,
4090 data: UiAccountData::Json(ParsedAccount {
4091 program: format!("{}", idl_v1.metadata.name).to_case(convert_case::Case::Kebab),
4092 parsed: serde_json::json!({
4093 "my_custom_data": 42,
4094 "another_field": "test",
4095 "bool": true,
4096 "pubkey": pubkey.to_string(),
4097 }),
4098 space: account.data.len() as u64,
4099 }),
4100 owner: idl_v1.address.clone(),
4101 executable: false,
4102 rent_epoch: 0,
4103 space: Some(account.data.len() as u64),
4104 };
4105 assert_eq!(ui_account, expected_account);
4106 }
4107
4108 {
4110 #[derive(borsh::BorshSerialize)]
4112 pub struct CustomAccount {
4113 pub my_custom_data: u64,
4114 pub another_field: String,
4115 pub pubkey: Pubkey,
4116 }
4117 let mut account_data = idl_v1.accounts[0].discriminator.clone();
4118 let pubkey = Pubkey::new_unique();
4119 CustomAccount {
4120 my_custom_data: 42,
4121 another_field: "test".to_string(),
4122 pubkey,
4123 }
4124 .serialize(&mut account_data)
4125 .unwrap();
4126
4127 let account = Account {
4128 lamports: 1000,
4129 data: account_data.clone(),
4130 owner: idl_v1.address.parse().unwrap(),
4131 executable: false,
4132 rent_epoch: 0,
4133 };
4134
4135 let ui_account = svm.encode_ui_account(
4136 &account_pubkey,
4137 &account,
4138 UiAccountEncoding::JsonParsed,
4139 None,
4140 None,
4141 );
4142 let base64_data = general_purpose::STANDARD.encode(&account_data);
4143 let expected_data = UiAccountData::Binary(base64_data, UiAccountEncoding::Base64);
4144 let expected_account = UiAccount {
4145 lamports: 1000,
4146 data: expected_data,
4147 owner: idl_v1.address.clone(),
4148 executable: false,
4149 rent_epoch: 0,
4150 space: Some(account.data.len() as u64),
4151 };
4152 assert_eq!(ui_account, expected_account);
4153
4154 svm.latest_epoch_info.absolute_slot = 100; let ui_account = svm.encode_ui_account(
4157 &account_pubkey,
4158 &account,
4159 UiAccountEncoding::JsonParsed,
4160 None,
4161 None,
4162 );
4163 let expected_account = UiAccount {
4164 lamports: 1000,
4165 data: UiAccountData::Json(ParsedAccount {
4166 program: format!("{}", idl_v1.metadata.name).to_case(convert_case::Case::Kebab),
4167 parsed: serde_json::json!({
4168 "my_custom_data": 42,
4169 "another_field": "test",
4170 "pubkey": pubkey.to_string(),
4171 }),
4172 space: account.data.len() as u64,
4173 }),
4174 owner: idl_v1.address.clone(),
4175 executable: false,
4176 rent_epoch: 0,
4177 space: Some(account.data.len() as u64),
4178 };
4179 assert_eq!(ui_account, expected_account);
4180 }
4181 }
4182
4183 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4184 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4185 #[test_case(TestType::no_db(); "with no db")]
4186 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4187 fn test_profiling_map_capacity_default(test_type: TestType) {
4188 let (svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4189 assert_eq!(svm.max_profiles, DEFAULT_PROFILING_MAP_CAPACITY);
4190 }
4191
4192 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4193 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4194 #[test_case(TestType::no_db(); "with no db")]
4195 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4196 fn test_profiling_map_capacity_set(test_type: TestType) {
4197 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4198 svm.set_profiling_map_capacity(10);
4199 assert_eq!(svm.max_profiles, 10);
4200 }
4201
4202 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4205 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4206 #[test_case(TestType::no_db(); "with no db")]
4207 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4208 fn test_apply_feature_config_empty(test_type: TestType) {
4209 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4210 let config = SvmFeatureConfig::new();
4211
4212 svm.apply_feature_config(&config);
4214 }
4215
4216 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4217 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4218 #[test_case(TestType::no_db(); "with no db")]
4219 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4220 fn test_apply_feature_config_enable_feature(test_type: TestType) {
4221 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4222
4223 let feature_id = enable_loader_v4::id();
4225 svm.feature_set.deactivate(&feature_id);
4226 assert!(!svm.feature_set.is_active(&feature_id));
4227
4228 let config = SvmFeatureConfig::new().enable(enable_loader_v4::id());
4230 svm.apply_feature_config(&config);
4231
4232 assert!(svm.feature_set.is_active(&feature_id));
4233 }
4234
4235 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4236 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4237 #[test_case(TestType::no_db(); "with no db")]
4238 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4239 fn test_apply_feature_config_disable_feature(test_type: TestType) {
4240 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4241
4242 let feature_id = disable_fees_sysvar::id();
4244 assert!(svm.feature_set.is_active(&feature_id));
4245
4246 let config = SvmFeatureConfig::new().disable(disable_fees_sysvar::id());
4248 svm.apply_feature_config(&config);
4249
4250 assert!(!svm.feature_set.is_active(&feature_id));
4251 }
4252
4253 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4254 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4255 #[test_case(TestType::no_db(); "with no db")]
4256 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4257 fn test_apply_feature_config_mainnet_defaults(test_type: TestType) {
4258 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4259 let config = SvmFeatureConfig::default_mainnet_features();
4260
4261 svm.apply_feature_config(&config);
4262
4263 assert!(!svm.feature_set.is_active(&enable_loader_v4::id()));
4265 assert!(
4266 !svm.feature_set
4267 .is_active(&enable_extend_program_checked::id())
4268 );
4269 assert!(!svm.feature_set.is_active(&blake3_syscall_enabled::id()));
4270 assert!(
4271 !svm.feature_set
4272 .is_active(&enable_sbpf_v1_deployment_and_execution::id())
4273 );
4274 assert!(
4275 !svm.feature_set
4276 .is_active(&formalize_loaded_transaction_data_size::id())
4277 );
4278 assert!(
4279 !svm.feature_set
4280 .is_active(&move_precompile_verification_to_svm::id())
4281 );
4282
4283 assert!(svm.feature_set.is_active(&disable_fees_sysvar::id()));
4285 assert!(svm.feature_set.is_active(&curve25519_syscall_enabled::id()));
4286 assert!(
4287 svm.feature_set
4288 .is_active(&enable_sbpf_v2_deployment_and_execution::id())
4289 );
4290 assert!(
4291 svm.feature_set
4292 .is_active(&enable_sbpf_v3_deployment_and_execution::id())
4293 );
4294 assert!(
4295 svm.feature_set
4296 .is_active(&raise_cpi_nesting_limit_to_8::id())
4297 );
4298 }
4299
4300 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4301 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4302 #[test_case(TestType::no_db(); "with no db")]
4303 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4304 fn test_apply_feature_config_mainnet_with_override(test_type: TestType) {
4305 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4306
4307 let config = SvmFeatureConfig::default_mainnet_features().enable(enable_loader_v4::id());
4309
4310 svm.apply_feature_config(&config);
4311
4312 assert!(svm.feature_set.is_active(&enable_loader_v4::id()));
4314
4315 assert!(!svm.feature_set.is_active(&blake3_syscall_enabled::id()));
4317 assert!(
4318 !svm.feature_set
4319 .is_active(&enable_extend_program_checked::id())
4320 );
4321 }
4322
4323 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4324 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4325 #[test_case(TestType::no_db(); "with no db")]
4326 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4327 fn test_apply_feature_config_multiple_changes(test_type: TestType) {
4328 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4329
4330 let config = SvmFeatureConfig::new()
4331 .enable(enable_loader_v4::id())
4332 .enable(enable_sbpf_v2_deployment_and_execution::id())
4333 .disable(disable_fees_sysvar::id())
4334 .disable(blake3_syscall_enabled::id());
4335
4336 svm.apply_feature_config(&config);
4337
4338 assert!(svm.feature_set.is_active(&enable_loader_v4::id()));
4339 assert!(
4340 svm.feature_set
4341 .is_active(&enable_sbpf_v2_deployment_and_execution::id())
4342 );
4343 assert!(!svm.feature_set.is_active(&disable_fees_sysvar::id()));
4344 assert!(!svm.feature_set.is_active(&blake3_syscall_enabled::id()));
4345 }
4346
4347 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4348 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4349 #[test_case(TestType::no_db(); "with no db")]
4350 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4351 fn test_apply_feature_config_preserves_native_mint(test_type: TestType) {
4352 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4353
4354 assert!(
4356 svm.inner
4357 .get_account(&spl_token_interface::native_mint::ID)
4358 .unwrap()
4359 .is_some()
4360 );
4361
4362 let config = SvmFeatureConfig::new().disable(disable_fees_sysvar::id());
4363 svm.apply_feature_config(&config);
4364
4365 assert!(
4367 svm.inner
4368 .get_account(&spl_token_interface::native_mint::ID)
4369 .unwrap()
4370 .is_some()
4371 );
4372 }
4373
4374 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4375 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4376 #[test_case(TestType::no_db(); "with no db")]
4377 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4378 fn test_apply_feature_config_idempotent(test_type: TestType) {
4379 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4380
4381 let config = SvmFeatureConfig::new()
4382 .enable(enable_loader_v4::id())
4383 .disable(disable_fees_sysvar::id());
4384
4385 svm.apply_feature_config(&config);
4387 svm.apply_feature_config(&config);
4388
4389 assert!(svm.feature_set.is_active(&enable_loader_v4::id()));
4391 assert!(!svm.feature_set.is_active(&disable_fees_sysvar::id()));
4392 }
4393
4394 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4397 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4398 #[test_case(TestType::no_db(); "with no db")]
4399 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4400 fn test_garbage_collected_account_tracking(test_type: TestType) {
4401 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4402
4403 let owner = Pubkey::new_unique();
4404 let account_pubkey = Pubkey::new_unique();
4405
4406 let account = Account {
4407 lamports: 1000000,
4408 data: vec![1, 2, 3, 4, 5],
4409 owner,
4410 executable: false,
4411 rent_epoch: 0,
4412 };
4413
4414 svm.set_account(&account_pubkey, account.clone()).unwrap();
4415
4416 assert!(svm.get_account(&account_pubkey).unwrap().is_some());
4417 assert!(!svm.closed_accounts.contains(&account_pubkey));
4418 assert_eq!(svm.get_account_owned_by(&owner).unwrap().len(), 1);
4419
4420 let empty_account = Account::default();
4421 svm.update_account_registries(&account_pubkey, &empty_account)
4422 .unwrap();
4423
4424 assert!(svm.closed_accounts.contains(&account_pubkey));
4425
4426 assert_eq!(svm.get_account_owned_by(&owner).unwrap().len(), 0);
4427
4428 let owned_accounts = svm.get_account_owned_by(&owner).unwrap();
4429 assert!(!owned_accounts.iter().any(|(pk, _)| *pk == account_pubkey));
4430 }
4431
4432 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4433 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4434 #[test_case(TestType::no_db(); "with no db")]
4435 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4436 fn test_garbage_collected_token_account_cleanup(test_type: TestType) {
4437 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4438
4439 let token_owner = Pubkey::new_unique();
4440 let delegate = Pubkey::new_unique();
4441 let mint = Pubkey::new_unique();
4442 let token_account_pubkey = Pubkey::new_unique();
4443
4444 let mut token_account_data = [0u8; TokenAccount::LEN];
4445 let token_account = TokenAccount {
4446 mint,
4447 owner: token_owner,
4448 amount: 1000,
4449 delegate: COption::Some(delegate),
4450 state: AccountState::Initialized,
4451 is_native: COption::None,
4452 delegated_amount: 500,
4453 close_authority: COption::None,
4454 };
4455 token_account.pack_into_slice(&mut token_account_data);
4456
4457 let account = Account {
4458 lamports: 2000000,
4459 data: token_account_data.to_vec(),
4460 owner: spl_token_interface::id(),
4461 executable: false,
4462 rent_epoch: 0,
4463 };
4464
4465 svm.set_account(&token_account_pubkey, account).unwrap();
4466
4467 assert_eq!(
4468 svm.get_token_accounts_by_owner(&token_owner).unwrap().len(),
4469 1
4470 );
4471 assert_eq!(svm.get_token_accounts_by_delegate(&delegate).len(), 1);
4472 assert!(!svm.closed_accounts.contains(&token_account_pubkey));
4473
4474 let empty_account = Account::default();
4475 svm.update_account_registries(&token_account_pubkey, &empty_account)
4476 .unwrap();
4477
4478 assert!(svm.closed_accounts.contains(&token_account_pubkey));
4479
4480 assert_eq!(
4481 svm.get_token_accounts_by_owner(&token_owner).unwrap().len(),
4482 0
4483 );
4484 assert_eq!(svm.get_token_accounts_by_delegate(&delegate).len(), 0);
4485 assert!(
4486 svm.token_accounts
4487 .get(&token_account_pubkey.to_string())
4488 .unwrap()
4489 .is_none()
4490 );
4491 }
4492
4493 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4494 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4495 #[test_case(TestType::no_db(); "with no db")]
4496 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4497 fn test_is_slot_in_valid_range(test_type: TestType) {
4498 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4499
4500 svm.genesis_slot = 100;
4502 svm.latest_epoch_info.absolute_slot = 110;
4503
4504 assert!(
4506 svm.is_slot_in_valid_range(100),
4507 "genesis_slot should be valid"
4508 );
4509 assert!(
4510 svm.is_slot_in_valid_range(105),
4511 "middle slot should be valid"
4512 );
4513 assert!(
4514 svm.is_slot_in_valid_range(110),
4515 "latest slot should be valid"
4516 );
4517
4518 assert!(
4520 !svm.is_slot_in_valid_range(99),
4521 "slot before genesis should be invalid"
4522 );
4523 assert!(
4524 !svm.is_slot_in_valid_range(111),
4525 "slot after latest should be invalid"
4526 );
4527 assert!(
4528 !svm.is_slot_in_valid_range(0),
4529 "slot 0 should be invalid when genesis > 0"
4530 );
4531 assert!(
4532 !svm.is_slot_in_valid_range(1000),
4533 "far future slot should be invalid"
4534 );
4535 }
4536
4537 #[test]
4538 fn test_is_slot_in_valid_range_genesis_zero() {
4539 let (mut svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
4540
4541 svm.genesis_slot = 0;
4543 svm.latest_epoch_info.absolute_slot = 50;
4544
4545 assert!(
4547 svm.is_slot_in_valid_range(0),
4548 "slot 0 should be valid when genesis = 0"
4549 );
4550 assert!(
4551 svm.is_slot_in_valid_range(25),
4552 "middle slot should be valid"
4553 );
4554 assert!(
4555 svm.is_slot_in_valid_range(50),
4556 "latest slot should be valid"
4557 );
4558 assert!(
4559 !svm.is_slot_in_valid_range(51),
4560 "slot after latest should be invalid"
4561 );
4562 }
4563
4564 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4565 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4566 #[test_case(TestType::no_db(); "with no db")]
4567 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4568 fn test_get_block_or_reconstruct_stored_block(test_type: TestType) {
4569 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4570
4571 svm.genesis_slot = 0;
4573 svm.latest_epoch_info.absolute_slot = 100;
4574
4575 let stored_block = BlockHeader {
4577 hash: "stored_block_hash".to_string(),
4578 previous_blockhash: "prev_hash".to_string(),
4579 parent_slot: 49,
4580 block_time: 1234567890,
4581 block_height: 50,
4582 signatures: vec![Signature::new_unique()],
4583 };
4584 svm.blocks.store(50, stored_block.clone()).unwrap();
4585
4586 let result = svm.get_block_or_reconstruct(50).unwrap();
4588 assert!(result.is_some(), "should return stored block");
4589 let block = result.unwrap();
4590 assert_eq!(block.hash, "stored_block_hash");
4591 assert_eq!(block.signatures.len(), 1);
4592 }
4593
4594 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4595 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4596 #[test_case(TestType::no_db(); "with no db")]
4597 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4598 fn test_get_block_or_reconstruct_empty_block(test_type: TestType) {
4599 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4600
4601 svm.genesis_slot = 0;
4603 svm.latest_epoch_info.absolute_slot = 100;
4604 svm.genesis_updated_at = 1000000; svm.slot_time = 400; let result = svm.get_block_or_reconstruct(50).unwrap();
4609 assert!(
4610 result.is_some(),
4611 "should reconstruct empty block for valid slot"
4612 );
4613
4614 let block = result.unwrap();
4615 assert!(
4617 block.signatures.is_empty(),
4618 "reconstructed block should have no signatures"
4619 );
4620 assert_eq!(block.block_height, 50);
4621 assert_eq!(block.parent_slot, 49);
4622
4623 assert_eq!(block.block_time, 1020);
4626 }
4627
4628 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4629 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4630 #[test_case(TestType::no_db(); "with no db")]
4631 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4632 fn test_get_block_or_reconstruct_out_of_range(test_type: TestType) {
4633 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4634
4635 svm.genesis_slot = 100;
4637 svm.latest_epoch_info.absolute_slot = 110;
4638
4639 let result = svm.get_block_or_reconstruct(50).unwrap();
4641 assert!(
4642 result.is_none(),
4643 "should return None for slot before genesis"
4644 );
4645
4646 let result = svm.get_block_or_reconstruct(200).unwrap();
4648 assert!(result.is_none(), "should return None for slot after latest");
4649 }
4650
4651 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4652 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4653 #[test_case(TestType::no_db(); "with no db")]
4654 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4655 #[allow(deprecated)]
4656 fn test_reconstruct_sysvars_recent_blockhashes(test_type: TestType) {
4657 use solana_sysvar::recent_blockhashes::RecentBlockhashes;
4658
4659 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4660
4661 svm.chain_tip = BlockIdentifier::new(10, "test_hash");
4663 svm.genesis_slot = 0;
4664 svm.latest_epoch_info.absolute_slot = 10;
4665
4666 svm.reconstruct_sysvars();
4667
4668 let recent_blockhashes = svm.inner.get_sysvar::<RecentBlockhashes>();
4670
4671 assert_eq!(recent_blockhashes.len(), 11);
4673
4674 let expected_hash = SyntheticBlockhash::new(10);
4676 assert_eq!(
4677 recent_blockhashes.first().unwrap().blockhash,
4678 *expected_hash.hash(),
4679 "First blockhash should match SyntheticBlockhash for chain_tip.index"
4680 );
4681
4682 let expected_last_hash = SyntheticBlockhash::new(0);
4684 assert_eq!(
4685 recent_blockhashes.last().unwrap().blockhash,
4686 *expected_last_hash.hash(),
4687 "Last blockhash should match SyntheticBlockhash for index 0"
4688 );
4689 }
4690
4691 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4692 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4693 #[test_case(TestType::no_db(); "with no db")]
4694 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4695 #[allow(deprecated)]
4696 fn test_reconstruct_sysvars_slot_hashes(test_type: TestType) {
4697 use solana_slot_hashes::SlotHashes;
4698
4699 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4700
4701 svm.chain_tip = BlockIdentifier::new(5, "test_hash");
4703 svm.genesis_slot = 100;
4704 svm.latest_epoch_info.absolute_slot = 105;
4705
4706 svm.reconstruct_sysvars();
4707
4708 let slot_hashes = svm.inner.get_sysvar::<SlotHashes>();
4710
4711 assert_eq!(slot_hashes.len(), 6);
4713
4714 let expected_hash_105 = SyntheticBlockhash::new(5);
4716 let hash_for_105 = slot_hashes.get(&105);
4717 assert!(hash_for_105.is_some(), "SlotHashes should contain slot 105");
4718 assert_eq!(
4719 hash_for_105.unwrap(),
4720 expected_hash_105.hash(),
4721 "Hash for slot 105 should match SyntheticBlockhash for index 5"
4722 );
4723
4724 let expected_hash_100 = SyntheticBlockhash::new(0);
4726 let hash_for_100 = slot_hashes.get(&100);
4727 assert!(hash_for_100.is_some(), "SlotHashes should contain slot 100");
4728 assert_eq!(
4729 hash_for_100.unwrap(),
4730 expected_hash_100.hash(),
4731 "Hash for slot 100 should match SyntheticBlockhash for index 0"
4732 );
4733 }
4734
4735 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4736 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4737 #[test_case(TestType::no_db(); "with no db")]
4738 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4739 fn test_reconstruct_sysvars_clock(test_type: TestType) {
4740 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4741
4742 svm.chain_tip = BlockIdentifier::new(50, "test_hash");
4744 svm.genesis_slot = 1000;
4745 svm.latest_epoch_info.absolute_slot = 1050;
4746 svm.latest_epoch_info.epoch = 5;
4747 svm.genesis_updated_at = 2_000_000; svm.slot_time = 400; svm.reconstruct_sysvars();
4751
4752 let clock = svm.inner.get_sysvar::<Clock>();
4754
4755 assert_eq!(clock.slot, 1050, "Clock slot should be absolute slot");
4756 assert_eq!(clock.epoch, 5, "Clock epoch should match latest_epoch_info");
4757
4758 assert_eq!(
4760 clock.unix_timestamp, 2020,
4761 "Clock unix_timestamp should be calculated correctly"
4762 );
4763 }
4764
4765 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4766 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4767 #[test_case(TestType::no_db(); "with no db")]
4768 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4769 #[allow(deprecated)]
4770 fn test_reconstruct_sysvars_max_blockhashes(test_type: TestType) {
4771 use solana_sysvar::recent_blockhashes::RecentBlockhashes;
4772
4773 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4774
4775 svm.chain_tip = BlockIdentifier::new(200, "test_hash");
4777 svm.genesis_slot = 0;
4778 svm.latest_epoch_info.absolute_slot = 200;
4779
4780 svm.reconstruct_sysvars();
4781
4782 let recent_blockhashes = svm.inner.get_sysvar::<RecentBlockhashes>();
4784
4785 assert_eq!(
4786 recent_blockhashes.len(),
4787 MAX_RECENT_BLOCKHASHES_STANDARD,
4788 "RecentBlockhashes should be capped at MAX_RECENT_BLOCKHASHES_STANDARD"
4789 );
4790
4791 let expected_hash = SyntheticBlockhash::new(200);
4793 assert_eq!(
4794 recent_blockhashes.first().unwrap().blockhash,
4795 *expected_hash.hash(),
4796 "First blockhash should match SyntheticBlockhash for chain_tip.index"
4797 );
4798
4799 let expected_last_hash = SyntheticBlockhash::new(51);
4801 assert_eq!(
4802 recent_blockhashes.last().unwrap().blockhash,
4803 *expected_last_hash.hash(),
4804 "Last blockhash should match SyntheticBlockhash for start_index"
4805 );
4806 }
4807
4808 #[test_case(TestType::sqlite(); "with on-disk sqlite db")]
4809 #[test_case(TestType::in_memory(); "with in-memory sqlite db")]
4810 #[test_case(TestType::no_db(); "with no db")]
4811 #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
4812 #[allow(deprecated)]
4813 fn test_reconstruct_sysvars_deterministic(test_type: TestType) {
4814 use solana_slot_hashes::SlotHashes;
4815 use solana_sysvar::recent_blockhashes::RecentBlockhashes;
4816
4817 let (mut svm, _events_rx, _geyser_rx) = test_type.initialize_svm();
4818
4819 svm.chain_tip = BlockIdentifier::new(25, "test_hash");
4821 svm.genesis_slot = 50;
4822 svm.latest_epoch_info.absolute_slot = 75;
4823 svm.latest_epoch_info.epoch = 2;
4824 svm.genesis_updated_at = 1_000_000;
4825 svm.slot_time = 400;
4826
4827 svm.reconstruct_sysvars();
4829 let blockhashes_1 = svm.inner.get_sysvar::<RecentBlockhashes>();
4830 let slot_hashes_1 = svm.inner.get_sysvar::<SlotHashes>();
4831 let clock_1 = svm.inner.get_sysvar::<Clock>();
4832
4833 svm.reconstruct_sysvars();
4835 let blockhashes_2 = svm.inner.get_sysvar::<RecentBlockhashes>();
4836 let slot_hashes_2 = svm.inner.get_sysvar::<SlotHashes>();
4837 let clock_2 = svm.inner.get_sysvar::<Clock>();
4838
4839 assert_eq!(blockhashes_1.len(), blockhashes_2.len());
4841 for (b1, b2) in blockhashes_1.iter().zip(blockhashes_2.iter()) {
4842 assert_eq!(
4843 b1.blockhash, b2.blockhash,
4844 "RecentBlockhashes should be deterministic"
4845 );
4846 }
4847
4848 assert_eq!(slot_hashes_1.len(), slot_hashes_2.len());
4849 assert_eq!(clock_1.slot, clock_2.slot);
4850 assert_eq!(clock_1.epoch, clock_2.epoch);
4851 assert_eq!(clock_1.unix_timestamp, clock_2.unix_timestamp);
4852 }
4853}