1#[cfg(feature = "prometheus")]
2use std::time::SystemTime;
3use std::{
4 cmp::Ordering,
5 collections::{BTreeMap, HashMap},
6 fmt,
7 path::PathBuf,
8 str::FromStr,
9 sync::{Arc, Mutex},
10};
11
12use blake3::Hash;
13use chrono::{DateTime, Local};
14use crossbeam_channel::Sender;
15use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
16use serde_with::{BytesOrString, serde_as};
17use solana_account::Account;
18use solana_account_decoder_client_types::{ParsedAccount, UiAccount, UiAccountEncoding};
19use solana_clock::{Clock, Epoch, Slot};
20use solana_epoch_info::EpochInfo;
21use solana_message::inner_instruction::InnerInstructionsList;
22use solana_pubkey::Pubkey;
23use solana_signature::Signature;
24use solana_transaction::versioned::VersionedTransaction;
25use solana_transaction_context::TransactionReturnData;
26use solana_transaction_error::TransactionError;
27use txtx_addon_kit::indexmap::IndexMap;
28use uuid::Uuid;
29
30use crate::DEFAULT_MAINNET_RPC_URL;
31
32pub const DEFAULT_RPC_PORT: u16 = 8899;
33pub const DEFAULT_WS_PORT: u16 = 8900;
34pub const DEFAULT_STUDIO_PORT: u16 = 8488;
35pub const CHANGE_TO_DEFAULT_STUDIO_PORT_ONCE_SUPERVISOR_MERGED: u16 = 18488;
36pub const DEFAULT_NETWORK_HOST: &str = "127.0.0.1";
37pub const DEFAULT_SLOT_TIME_MS: u64 = 400;
38pub type Idl = anchor_lang_idl::types::Idl;
39pub const DEFAULT_PROFILING_MAP_CAPACITY: usize = 200;
40
41#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
42pub struct TransactionMetadata {
43 pub signature: Signature,
44 pub logs: Vec<String>,
45 pub inner_instructions: InnerInstructionsList,
46 pub compute_units_consumed: u64,
47 pub return_data: TransactionReturnData,
48 pub fee: u64,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub enum TransactionConfirmationStatus {
54 Processed,
55 Confirmed,
56 Finalized,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
60pub enum BlockProductionMode {
61 #[default]
62 Clock,
63 Transaction,
64 Manual,
65}
66
67impl fmt::Display for BlockProductionMode {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 BlockProductionMode::Clock => write!(f, "clock"),
71 BlockProductionMode::Transaction => write!(f, "transaction"),
72 BlockProductionMode::Manual => write!(f, "manual"),
73 }
74 }
75}
76
77impl FromStr for BlockProductionMode {
78 type Err = String;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 match s.to_lowercase().as_str() {
82 "clock" => Ok(BlockProductionMode::Clock),
83 "transaction" => Ok(BlockProductionMode::Transaction),
84 "manual" => Ok(BlockProductionMode::Manual),
85 _ => Err(format!(
86 "Invalid block production mode: {}. Valid values are: clock, transaction, manual",
87 s
88 )),
89 }
90 }
91}
92
93#[derive(Debug)]
94pub enum SubgraphEvent {
95 EndpointReady,
96 InfoLog(DateTime<Local>, String),
97 ErrorLog(DateTime<Local>, String),
98 WarnLog(DateTime<Local>, String),
99 DebugLog(DateTime<Local>, String),
100 Shutdown,
101}
102
103impl SubgraphEvent {
104 pub fn info<S>(msg: S) -> Self
105 where
106 S: Into<String>,
107 {
108 Self::InfoLog(Local::now(), msg.into())
109 }
110
111 pub fn warn<S>(msg: S) -> Self
112 where
113 S: Into<String>,
114 {
115 Self::WarnLog(Local::now(), msg.into())
116 }
117
118 pub fn error<S>(msg: S) -> Self
119 where
120 S: Into<String>,
121 {
122 Self::ErrorLog(Local::now(), msg.into())
123 }
124
125 pub fn debug<S>(msg: S) -> Self
126 where
127 S: Into<String>,
128 {
129 Self::DebugLog(Local::now(), msg.into())
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
135#[serde(rename_all = "camelCase")]
136pub struct ComputeUnitsEstimationResult {
137 pub success: bool,
138 pub compute_units_consumed: u64,
139 pub log_messages: Option<Vec<String>>,
140 pub error_message: Option<String>,
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct KeyedProfileResult {
146 pub slot: u64,
147 pub key: UuidOrSignature,
148 pub instruction_profiles: Option<Vec<ProfileResult>>,
149 pub transaction_profile: ProfileResult,
150 #[serde(with = "pubkey_account_map")]
151 pub readonly_account_states: HashMap<Pubkey, Account>,
152}
153
154impl KeyedProfileResult {
155 pub fn new(
156 slot: u64,
157 key: UuidOrSignature,
158 instruction_profiles: Option<Vec<ProfileResult>>,
159 transaction_profile: ProfileResult,
160 readonly_account_states: HashMap<Pubkey, Account>,
161 ) -> Self {
162 Self {
163 slot,
164 key,
165 instruction_profiles,
166 transaction_profile,
167 readonly_account_states,
168 }
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub struct ProfileResult {
174 #[serde(with = "pubkey_option_account_map")]
175 pub pre_execution_capture: ExecutionCapture,
176 #[serde(with = "pubkey_option_account_map")]
177 pub post_execution_capture: ExecutionCapture,
178 pub compute_units_consumed: u64,
179 pub log_messages: Option<Vec<String>>,
180 pub error_message: Option<String>,
181}
182
183pub type ExecutionCapture = BTreeMap<Pubkey, Option<Account>>;
184
185impl ProfileResult {
186 pub fn new(
187 pre_execution_capture: ExecutionCapture,
188 post_execution_capture: ExecutionCapture,
189 compute_units_consumed: u64,
190 log_messages: Option<Vec<String>>,
191 error_message: Option<String>,
192 ) -> Self {
193 Self {
194 pre_execution_capture,
195 post_execution_capture,
196 compute_units_consumed,
197 log_messages,
198 error_message,
199 }
200 }
201}
202
203#[derive(Debug, Clone, PartialEq)]
204pub enum AccountProfileState {
205 Readonly,
206 Writable(AccountChange),
207}
208
209impl AccountProfileState {
210 pub fn new(
211 pubkey: Pubkey,
212 pre_account: Option<Account>,
213 post_account: Option<Account>,
214 readonly_accounts: &[Pubkey],
215 ) -> Self {
216 if readonly_accounts.contains(&pubkey) {
217 return AccountProfileState::Readonly;
218 }
219
220 match (pre_account, post_account) {
221 (None, Some(post_account)) => {
222 AccountProfileState::Writable(AccountChange::Create(post_account))
223 }
224 (Some(pre_account), None) => {
225 AccountProfileState::Writable(AccountChange::Delete(pre_account))
226 }
227 (Some(pre_account), Some(post_account)) if pre_account == post_account => {
228 AccountProfileState::Writable(AccountChange::Unchanged(Some(pre_account)))
229 }
230 (Some(pre_account), Some(post_account)) => {
231 AccountProfileState::Writable(AccountChange::Update(pre_account, post_account))
232 }
233 (None, None) => AccountProfileState::Writable(AccountChange::Unchanged(None)),
234 }
235 }
236}
237
238#[derive(Debug, Clone, PartialEq)]
239pub enum AccountChange {
240 Create(Account),
241 Update(Account, Account),
242 Delete(Account),
243 Unchanged(Option<Account>),
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct RpcProfileResultConfig {
249 pub encoding: Option<UiAccountEncoding>,
250 pub depth: Option<RpcProfileDepth>,
251}
252
253impl Default for RpcProfileResultConfig {
254 fn default() -> Self {
255 Self {
256 encoding: Some(UiAccountEncoding::JsonParsed),
257 depth: Some(RpcProfileDepth::default()),
258 }
259 }
260}
261
262#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
263#[serde(rename_all = "camelCase")]
264pub enum RpcProfileDepth {
265 Transaction,
266 #[default]
267 Instruction,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
271#[serde(rename_all = "camelCase")]
272pub struct UiKeyedProfileResult {
273 pub slot: u64,
274 pub key: UuidOrSignature,
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub instruction_profiles: Option<Vec<UiProfileResult>>,
277 pub transaction_profile: UiProfileResult,
278 #[serde(with = "profile_state_map")]
279 pub readonly_account_states: IndexMap<Pubkey, UiAccount>,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
283#[serde(rename_all = "camelCase")]
284pub struct UiProfileResult {
285 #[serde(with = "profile_state_map")]
286 pub account_states: IndexMap<Pubkey, UiAccountProfileState>,
287 pub compute_units_consumed: u64,
288 pub log_messages: Option<Vec<String>>,
289 pub error_message: Option<String>,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
293#[serde(rename_all = "camelCase", tag = "type", content = "accountChange")]
294#[allow(clippy::large_enum_variant)]
295pub enum UiAccountProfileState {
296 Readonly,
297 Writable(UiAccountChange),
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
301#[serde(rename_all = "camelCase", tag = "type", content = "data")]
302pub enum UiAccountChange {
303 Create(UiAccount),
304 Update(UiAccount, UiAccount),
305 Delete(UiAccount),
306 Unchanged(Option<UiAccount>),
308}
309
310pub mod profile_state_map {
320 use super::*;
321
322 pub fn serialize<S, T>(map: &IndexMap<Pubkey, T>, serializer: S) -> Result<S::Ok, S::Error>
323 where
324 S: Serializer,
325 T: Serialize,
326 {
327 let str_map: IndexMap<String, &T> = map.iter().map(|(k, v)| (k.to_string(), v)).collect();
328 str_map.serialize(serializer)
329 }
330
331 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<IndexMap<Pubkey, T>, D::Error>
332 where
333 D: Deserializer<'de>,
334 T: Deserialize<'de>,
335 {
336 let str_map: IndexMap<String, T> = IndexMap::deserialize(deserializer)?;
337 str_map
338 .into_iter()
339 .map(|(k, v)| {
340 Pubkey::from_str(&k)
341 .map(|pk| (pk, v))
342 .map_err(serde::de::Error::custom)
343 })
344 .collect()
345 }
346}
347
348pub mod pubkey_account_map {
350 use super::*;
351
352 pub fn serialize<S>(map: &HashMap<Pubkey, Account>, serializer: S) -> Result<S::Ok, S::Error>
353 where
354 S: Serializer,
355 {
356 let str_map: HashMap<String, &Account> =
357 map.iter().map(|(k, v)| (k.to_string(), v)).collect();
358 str_map.serialize(serializer)
359 }
360
361 pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<Pubkey, Account>, D::Error>
362 where
363 D: Deserializer<'de>,
364 {
365 let str_map: HashMap<String, Account> = HashMap::deserialize(deserializer)?;
366 str_map
367 .into_iter()
368 .map(|(k, v)| {
369 Pubkey::from_str(&k)
370 .map(|pk| (pk, v))
371 .map_err(serde::de::Error::custom)
372 })
373 .collect()
374 }
375}
376
377pub mod pubkey_option_account_map {
379 use super::*;
380
381 pub fn serialize<S>(
382 map: &BTreeMap<Pubkey, Option<Account>>,
383 serializer: S,
384 ) -> Result<S::Ok, S::Error>
385 where
386 S: Serializer,
387 {
388 let str_map: BTreeMap<String, &Option<Account>> =
389 map.iter().map(|(k, v)| (k.to_string(), v)).collect();
390 str_map.serialize(serializer)
391 }
392
393 pub fn deserialize<'de, D>(
394 deserializer: D,
395 ) -> Result<BTreeMap<Pubkey, Option<Account>>, D::Error>
396 where
397 D: Deserializer<'de>,
398 {
399 let str_map: BTreeMap<String, Option<Account>> = BTreeMap::deserialize(deserializer)?;
400 str_map
401 .into_iter()
402 .map(|(k, v)| {
403 Pubkey::from_str(&k)
404 .map(|pk| (pk, v))
405 .map_err(serde::de::Error::custom)
406 })
407 .collect()
408 }
409}
410
411#[derive(Debug)]
412pub enum SimnetEvent {
413 Ready(u64),
415 Connected(String),
416 Aborted(String),
417 Shutdown,
418 SystemClockUpdated(Clock),
419 ClockUpdate(ClockCommand),
420 EpochInfoUpdate(EpochInfo),
421 BlockHashExpired,
422 InfoLog(DateTime<Local>, String),
423 ErrorLog(DateTime<Local>, String),
424 WarnLog(DateTime<Local>, String),
425 DebugLog(DateTime<Local>, String),
426 PluginLoaded(String),
427 TransactionReceived(DateTime<Local>, VersionedTransaction),
428 TransactionProcessed(
429 DateTime<Local>,
430 TransactionMetadata,
431 Option<TransactionError>,
432 ),
433 AccountUpdate(DateTime<Local>, Pubkey),
434 TaggedProfile {
435 result: KeyedProfileResult,
436 tag: String,
437 timestamp: DateTime<Local>,
438 },
439 RunbookStarted(String),
440 RunbookCompleted(String, Option<Vec<String>>),
441}
442
443impl SimnetEvent {
444 pub fn info<S>(msg: S) -> Self
445 where
446 S: Into<String>,
447 {
448 Self::InfoLog(Local::now(), msg.into())
449 }
450
451 pub fn warn<S>(msg: S) -> Self
452 where
453 S: Into<String>,
454 {
455 Self::WarnLog(Local::now(), msg.into())
456 }
457
458 pub fn error<S>(msg: S) -> Self
459 where
460 S: Into<String>,
461 {
462 Self::ErrorLog(Local::now(), msg.into())
463 }
464
465 pub fn debug<S>(msg: S) -> Self
466 where
467 S: Into<String>,
468 {
469 Self::DebugLog(Local::now(), msg.into())
470 }
471
472 pub fn transaction_processed(meta: TransactionMetadata, err: Option<TransactionError>) -> Self {
473 Self::TransactionProcessed(Local::now(), meta, err)
474 }
475
476 pub fn transaction_received(tx: VersionedTransaction) -> Self {
477 Self::TransactionReceived(Local::now(), tx)
478 }
479
480 pub fn account_update(pubkey: Pubkey) -> Self {
481 Self::AccountUpdate(Local::now(), pubkey)
482 }
483
484 pub fn tagged_profile(result: KeyedProfileResult, tag: String) -> Self {
485 Self::TaggedProfile {
486 result,
487 tag,
488 timestamp: Local::now(),
489 }
490 }
491
492 pub fn account_update_msg(&self) -> String {
493 match self {
494 SimnetEvent::AccountUpdate(_, pubkey) => {
495 format!("Account {} updated.", pubkey)
496 }
497 _ => unreachable!("This function should only be called for AccountUpdate events"),
498 }
499 }
500
501 pub fn epoch_info_update_msg(&self) -> String {
502 match self {
503 SimnetEvent::EpochInfoUpdate(epoch_info) => {
504 format!(
505 "Datasource connection successful. Epoch {} / Slot index {} / Slot {}.",
506 epoch_info.epoch, epoch_info.slot_index, epoch_info.absolute_slot
507 )
508 }
509 _ => unreachable!("This function should only be called for EpochInfoUpdate events"),
510 }
511 }
512
513 pub fn plugin_loaded_msg(&self) -> String {
514 match self {
515 SimnetEvent::PluginLoaded(plugin_name) => {
516 format!("Plugin {} successfully loaded.", plugin_name)
517 }
518 _ => unreachable!("This function should only be called for PluginLoaded events"),
519 }
520 }
521
522 pub fn clock_update_msg(&self) -> String {
523 match self {
524 SimnetEvent::SystemClockUpdated(clock) => {
525 format!("Clock ticking (epoch {}, slot {})", clock.epoch, clock.slot)
526 }
527 _ => {
528 unreachable!("This function should only be called for SystemClockUpdated events")
529 }
530 }
531 }
532}
533
534#[derive(Debug)]
535pub enum TransactionStatusEvent {
536 Success(TransactionConfirmationStatus),
537 SimulationFailure((TransactionError, TransactionMetadata)),
538 ExecutionFailure((TransactionError, TransactionMetadata)),
539 VerificationFailure(String),
540}
541
542#[derive(Debug)]
543pub enum SimnetCommand {
544 SlotForward(Option<Hash>),
545 SlotBackward(Option<Hash>),
546 CommandClock(Option<(Hash, String)>, ClockCommand),
547 UpdateInternalClock(Option<(Hash, String)>, Clock),
548 UpdateInternalClockWithConfirmation(Option<(Hash, String)>, Clock, Sender<EpochInfo>),
549 UpdateBlockProductionMode(BlockProductionMode),
550 ProcessTransaction(
551 Option<(Hash, String)>,
552 VersionedTransaction,
553 Sender<TransactionStatusEvent>,
554 bool,
555 Option<bool>,
556 ),
557 Terminate(Option<(Hash, String)>),
558 StartRunbookExecution(String),
559 CompleteRunbookExecution(String, Option<Vec<String>>),
560 FetchRemoteAccounts(Vec<Pubkey>, String),
561 AirdropProcessed,
562}
563
564#[derive(Debug)]
565pub enum ClockCommand {
566 Pause,
567 PauseWithConfirmation(Sender<EpochInfo>),
569 Resume,
570 Toggle,
571 UpdateSlotInterval(u64),
572}
573
574pub enum ClockEvent {
575 Tick,
576 ExpireBlockHash,
577}
578
579#[derive(Clone, Debug, Default, Serialize)]
580pub struct SanitizedConfig {
581 pub rpc_url: String,
582 pub ws_url: String,
583 pub rpc_datasource_url: Option<String>,
584 pub studio_url: String,
585 pub graphql_query_route_url: String,
586 pub version: String,
587 pub workspace: Option<String>,
588}
589
590#[derive(Clone, Debug, Default, Serialize, Deserialize)]
591pub struct SurfpoolConfig {
592 pub simnets: Vec<SimnetConfig>,
593 pub rpc: RpcConfig,
594 pub subgraph: SubgraphConfig,
595 pub studio: StudioConfig,
596 pub plugin_config_path: Vec<PathBuf>,
597}
598
599#[derive(Clone, Debug, Serialize, Deserialize)]
600pub struct SimnetConfig {
601 pub offline_mode: bool,
602 pub remote_rpc_url: Option<String>,
603 pub slot_time: u64,
604 pub block_production_mode: BlockProductionMode,
605 pub airdrop_addresses: Vec<Pubkey>,
606 pub airdrop_token_amount: u64,
607 pub expiry: Option<u64>,
608 pub instruction_profiling_enabled: bool,
609 pub max_profiles: usize,
610 pub log_bytes_limit: Option<usize>,
611 pub skip_signature_verification: bool,
612 #[serde(default)]
613 pub skip_blockhash_check: bool,
614 pub surfnet_id: String,
617 pub snapshot: BTreeMap<String, Option<AccountSnapshot>>,
620}
621
622impl Default for SimnetConfig {
623 fn default() -> Self {
624 Self {
625 offline_mode: false,
626 remote_rpc_url: Some(DEFAULT_MAINNET_RPC_URL.to_string()),
627 slot_time: DEFAULT_SLOT_TIME_MS, block_production_mode: BlockProductionMode::Clock,
629 airdrop_addresses: vec![],
630 airdrop_token_amount: 0,
631 expiry: None,
632 instruction_profiling_enabled: true,
633 max_profiles: DEFAULT_PROFILING_MAP_CAPACITY,
634 log_bytes_limit: Some(10_000),
635 skip_signature_verification: false,
636 skip_blockhash_check: false,
637 surfnet_id: "default".to_string(),
638 snapshot: BTreeMap::new(),
639 }
640 }
641}
642
643impl SimnetConfig {
644 pub fn get_sanitized_datasource_url(&self) -> Option<String> {
648 let raw = self.remote_rpc_url.as_ref()?;
649
650 if let Ok(url) = url::Url::parse(raw) {
651 let scheme = url.scheme();
652 let host = url.host_str()?;
653 Some(format!("{}://{}", scheme, host))
654 } else {
655 None
656 }
657 }
658}
659
660#[derive(Clone, Debug, Default, Serialize, Deserialize)]
661pub struct SubgraphConfig {}
662
663pub const DEFAULT_GOSSIP_PORT: u16 = 8001;
664pub const DEFAULT_TPU_PORT: u16 = 8003;
665pub const DEFAULT_TPU_QUIC_PORT: u16 = 8004;
666
667#[derive(Clone, Debug, Serialize, Deserialize)]
668pub struct RpcConfig {
669 pub bind_host: String,
670 pub bind_port: u16,
671 pub ws_port: u16,
672 pub gossip_port: u16,
673 pub tpu_port: u16,
674 pub tpu_quic_port: u16,
675}
676
677impl RpcConfig {
678 pub fn get_rpc_base_url(&self) -> String {
679 format!("{}:{}", self.bind_host, self.bind_port)
680 }
681 pub fn get_ws_base_url(&self) -> String {
682 format!("{}:{}", self.bind_host, self.ws_port)
683 }
684}
685
686impl Default for RpcConfig {
687 fn default() -> Self {
688 Self {
689 bind_host: DEFAULT_NETWORK_HOST.to_string(),
690 bind_port: DEFAULT_RPC_PORT,
691 ws_port: DEFAULT_WS_PORT,
692 gossip_port: DEFAULT_GOSSIP_PORT,
693 tpu_port: DEFAULT_TPU_PORT,
694 tpu_quic_port: DEFAULT_TPU_QUIC_PORT,
695 }
696 }
697}
698
699#[derive(Clone, Debug, Serialize, Deserialize)]
700pub struct StudioConfig {
701 pub bind_host: String,
702 pub bind_port: u16,
703}
704
705impl StudioConfig {
706 pub fn get_studio_base_url(&self) -> String {
707 format!("{}:{}", self.bind_host, self.bind_port)
708 }
709}
710
711impl Default for StudioConfig {
712 fn default() -> Self {
713 Self {
714 bind_host: DEFAULT_NETWORK_HOST.to_string(),
715 bind_port: CHANGE_TO_DEFAULT_STUDIO_PORT_ONCE_SUPERVISOR_MERGED,
716 }
717 }
718}
719
720#[derive(Serialize, Deserialize, Clone, Debug)]
721#[serde(rename_all = "snake_case")]
722pub struct CreateSurfnetRequest {
723 pub domain: String,
724 pub block_production_mode: BlockProductionMode,
725 pub datasource_rpc_url: String,
726 pub settings: Option<CloudSurfnetSettings>,
727}
728
729#[derive(Serialize, Deserialize, Clone, Debug, Default)]
730#[serde(rename_all = "snake_case", default)]
731pub struct CloudSurfnetSettings {
732 pub database_url: Option<String>,
733 pub profiling_disabled: Option<bool>,
734 #[serde(skip_serializing_if = "Option::is_none")]
735 pub gating: Option<CloudSurfnetRpcGating>,
736}
737
738#[derive(Serialize, Deserialize, Clone, Debug, Default)]
739#[serde(rename_all = "snake_case", default)]
740pub struct CloudSurfnetRpcGating {
741 pub private_methods_secret_token: Option<String>,
742 pub private_methods: Vec<String>,
743 pub public_methods: Vec<String>,
744 pub disabled_methods: Vec<String>,
745}
746
747impl CloudSurfnetRpcGating {
748 pub fn restricted() -> CloudSurfnetRpcGating {
749 CloudSurfnetRpcGating {
750 private_methods: vec![],
751 private_methods_secret_token: None,
752 public_methods: vec![],
753 disabled_methods: vec![
754 "surfnet_cloneProgramAccount".into(),
755 "surfnet_profileTransaction".into(),
756 "surfnet_getProfileResultsByTag".into(),
757 "surfnet_setSupply".into(),
758 "surfnet_setProgramAuthority".into(),
759 "surfnet_getTransactionProfile".into(),
760 "surfnet_registerIdl".into(),
761 "surfnet_getActiveIdl".into(),
762 "surfnet_getLocalSignatures".into(),
763 "surfnet_timeTravel".into(),
764 "surfnet_pauseClock".into(),
765 "surfnet_resumeClock".into(),
766 "surfnet_resetAccount".into(),
767 "surfnet_resetNetwork".into(),
768 "surfnet_exportSnapshot".into(),
769 "surfnet_offlineAccount".into(),
770 "surfnet_streamAccount".into(),
771 "surfnet_streamAccounts".into(),
772 "surfnet_getStreamedAccounts".into(),
773 ],
774 }
775 }
776}
777
778#[derive(Serialize, Deserialize)]
779#[serde(rename_all = "snake_case")]
780pub struct CreateNetworkRequest {
781 pub workspace_id: Uuid,
782 pub name: String,
783 pub description: Option<String>,
784 pub datasource_rpc_url: String,
785 pub block_production_mode: BlockProductionMode,
786 pub profiling_enabled: Option<bool>,
787}
788
789impl CreateNetworkRequest {
790 pub fn new(
791 workspace_id: Uuid,
792 name: String,
793 description: Option<String>,
794 datasource_rpc_url: String,
795 block_production_mode: BlockProductionMode,
796 profiling_enabled: bool,
797 ) -> Self {
798 Self {
799 workspace_id,
800 name,
801 description,
802 datasource_rpc_url,
803 block_production_mode,
804 profiling_enabled: Some(profiling_enabled),
805 }
806 }
807}
808
809#[derive(Serialize, Deserialize)]
810pub struct CreateNetworkResponse {
811 pub rpc_url: String,
812}
813
814#[derive(Serialize, Deserialize)]
815pub struct DeleteNetworkRequest {
816 pub workspace_id: Uuid,
817 pub network_id: Uuid,
818}
819
820impl DeleteNetworkRequest {
821 pub fn new(workspace_id: Uuid, network_id: Uuid) -> Self {
822 Self {
823 workspace_id,
824 network_id,
825 }
826 }
827}
828
829#[derive(Serialize, Deserialize)]
830pub struct DeleteNetworkResponse;
831
832#[serde_as]
833#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
834#[serde(rename_all = "camelCase")]
835pub struct AccountUpdate {
836 pub lamports: Option<u64>,
838 #[serde_as(as = "Option<BytesOrString>")]
840 pub data: Option<Vec<u8>>,
841 pub owner: Option<String>,
843 pub executable: Option<bool>,
845 pub rent_epoch: Option<Epoch>,
847}
848
849#[derive(Debug, Clone)]
850pub enum SetSomeAccount {
851 Account(String),
852 NoAccount,
853}
854
855impl<'de> Deserialize<'de> for SetSomeAccount {
856 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
857 where
858 D: Deserializer<'de>,
859 {
860 struct SetSomeAccountVisitor;
861
862 impl<'de> Visitor<'de> for SetSomeAccountVisitor {
863 type Value = SetSomeAccount;
864
865 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
866 formatter.write_str("a Pubkey String or the String 'null'")
867 }
868
869 fn visit_some<D_>(self, deserializer: D_) -> std::result::Result<Self::Value, D_::Error>
870 where
871 D_: Deserializer<'de>,
872 {
873 Deserialize::deserialize(deserializer).map(|v: String| match v.as_str() {
874 "null" => SetSomeAccount::NoAccount,
875 _ => SetSomeAccount::Account(v.to_string()),
876 })
877 }
878 }
879
880 deserializer.deserialize_option(SetSomeAccountVisitor)
881 }
882}
883
884impl Serialize for SetSomeAccount {
885 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
886 where
887 S: Serializer,
888 {
889 match self {
890 SetSomeAccount::Account(val) => serializer.serialize_str(val),
891 SetSomeAccount::NoAccount => serializer.serialize_str("null"),
892 }
893 }
894}
895
896#[serde_as]
897#[derive(Debug, Clone, Default, Serialize, Deserialize)]
898#[serde(rename_all = "camelCase")]
899pub struct TokenAccountUpdate {
900 pub amount: Option<u64>,
902 pub delegate: Option<SetSomeAccount>,
904 pub state: Option<String>,
906 pub delegated_amount: Option<u64>,
908 pub close_authority: Option<SetSomeAccount>,
910}
911
912#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
914pub struct SupplyUpdate {
915 pub total: Option<u64>,
916 pub circulating: Option<u64>,
917 pub non_circulating: Option<u64>,
918 pub non_circulating_accounts: Option<Vec<String>>,
919}
920
921#[derive(Clone, Debug, PartialEq, Copy)]
922pub enum UuidOrSignature {
923 Uuid(Uuid),
924 Signature(Signature),
925}
926
927impl std::fmt::Display for UuidOrSignature {
928 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
929 match self {
930 UuidOrSignature::Uuid(uuid) => write!(f, "{}", uuid),
931 UuidOrSignature::Signature(signature) => write!(f, "{}", signature),
932 }
933 }
934}
935
936impl<'de> Deserialize<'de> for UuidOrSignature {
937 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
938 where
939 D: Deserializer<'de>,
940 {
941 let s = String::deserialize(deserializer)?;
942
943 if let Ok(uuid) = Uuid::parse_str(&s) {
944 return Ok(UuidOrSignature::Uuid(uuid));
945 }
946
947 if let Ok(signature) = s.parse::<Signature>() {
948 return Ok(UuidOrSignature::Signature(signature));
949 }
950
951 Err(serde::de::Error::custom(
952 "expected a Uuid or a valid Solana Signature",
953 ))
954 }
955}
956
957impl Serialize for UuidOrSignature {
958 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
959 where
960 S: Serializer,
961 {
962 match self {
963 UuidOrSignature::Uuid(uuid) => serializer.serialize_str(&uuid.to_string()),
964 UuidOrSignature::Signature(signature) => {
965 serializer.serialize_str(&signature.to_string())
966 }
967 }
968 }
969}
970
971#[derive(Debug, Clone, Deserialize, Serialize)]
972pub enum DataIndexingCommand {
973 ProcessCollection(Uuid),
974 ProcessCollectionEntriesPack(Uuid, Vec<u8>),
975}
976
977#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
978#[serde(rename_all = "camelCase")]
979pub struct JitoBundleStatus {
980 #[serde(rename = "bundle_id")]
981 pub bundle_id: String,
982 pub transactions: Vec<String>,
983 pub slot: u64,
984 pub confirmation_status: solana_transaction_status::TransactionConfirmationStatus,
985 pub err: std::result::Result<(), TransactionError>,
986}
987
988#[derive(Debug, Clone, Serialize, Deserialize)]
990pub struct VersionedIdl(pub Slot, pub Idl);
991
992impl PartialEq for VersionedIdl {
994 fn eq(&self, other: &Self) -> bool {
995 self.0 == other.0
996 }
997}
998
999impl Eq for VersionedIdl {}
1000
1001impl PartialOrd for VersionedIdl {
1002 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1003 Some(self.cmp(other))
1004 }
1005}
1006
1007impl Ord for VersionedIdl {
1008 fn cmp(&self, other: &Self) -> Ordering {
1009 self.0.cmp(&other.0)
1010 }
1011}
1012
1013#[derive(Debug, Clone)]
1014pub struct FifoMap<K, V> {
1015 map: IndexMap<K, V>,
1017}
1018
1019impl<K: std::hash::Hash + Eq, V> Default for FifoMap<K, V> {
1020 fn default() -> Self {
1021 Self::new(DEFAULT_PROFILING_MAP_CAPACITY)
1022 }
1023}
1024impl<K: std::hash::Hash + Eq, V> FifoMap<K, V> {
1025 pub fn new(capacity: usize) -> Self {
1026 Self {
1027 map: IndexMap::with_capacity(capacity),
1028 }
1029 }
1030
1031 pub fn capacity(&self) -> usize {
1032 self.map.capacity()
1033 }
1034
1035 pub fn len(&self) -> usize {
1036 self.map.len()
1037 }
1038
1039 pub fn clear(&mut self) {
1040 self.map.clear();
1041 }
1042
1043 pub fn is_empty(&self) -> bool {
1044 self.map.is_empty()
1045 }
1046
1047 pub fn insert(&mut self, key: K, value: V) -> (Option<V>, Option<K>) {
1052 if self.map.contains_key(&key) {
1053 return (self.map.insert(key, value), None);
1055 }
1056 let evicted_key = if self.map.len() == self.map.capacity() {
1057 self.map.shift_remove_index(0).map(|(k, _)| k)
1061 } else {
1062 None
1063 };
1064 self.map.insert(key, value);
1065 (None, evicted_key)
1066 }
1067
1068 pub fn get(&self, key: &K) -> Option<&V> {
1069 self.map.get(key)
1070 }
1071
1072 pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
1073 self.map.get_mut(key)
1074 }
1075
1076 pub fn contains_key(&self, key: &K) -> bool {
1077 self.map.contains_key(key)
1078 }
1079
1080 pub fn remove(&mut self, key: &K) -> Option<V> {
1082 self.map.shift_remove(key)
1083 }
1084
1085 pub fn iter(&self) -> impl ExactSizeIterator<Item = (&K, &V)> {
1088 self.map.iter()
1089 }
1090}
1091
1092#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1093#[serde(rename_all = "camelCase")]
1094pub struct AccountSnapshot {
1095 pub lamports: u64,
1096 pub owner: String,
1097 pub executable: bool,
1098 pub rent_epoch: u64,
1099 pub data: String,
1101 pub parsed_data: Option<ParsedAccount>,
1103}
1104
1105impl AccountSnapshot {
1106 pub fn new(
1107 lamports: u64,
1108 owner: String,
1109 executable: bool,
1110 rent_epoch: u64,
1111 data: String,
1112 parsed_data: Option<ParsedAccount>,
1113 ) -> Self {
1114 Self {
1115 lamports,
1116 owner,
1117 executable,
1118 rent_epoch,
1119 data,
1120 parsed_data,
1121 }
1122 }
1123}
1124
1125#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1126#[serde(rename_all = "camelCase")]
1127pub struct ExportSnapshotConfig {
1128 pub include_parsed_accounts: Option<bool>,
1129 pub filter: Option<ExportSnapshotFilter>,
1130 pub scope: ExportSnapshotScope,
1131}
1132
1133#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1134#[serde(rename_all = "camelCase")]
1135pub enum ExportSnapshotScope {
1136 #[default]
1137 Network,
1138 PreTransaction(String),
1139}
1140
1141#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1142#[serde(rename_all = "camelCase")]
1143pub struct ExportSnapshotFilter {
1144 pub include_program_accounts: Option<bool>,
1145 pub include_accounts: Option<Vec<String>>,
1146 pub exclude_accounts: Option<Vec<String>>,
1147}
1148
1149#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1150#[serde(rename_all = "camelCase")]
1151pub struct ResetAccountConfig {
1152 pub include_owned_accounts: Option<bool>,
1153}
1154
1155impl Default for ResetAccountConfig {
1156 fn default() -> Self {
1157 Self {
1158 include_owned_accounts: Some(false),
1159 }
1160 }
1161}
1162
1163#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1164#[serde(rename_all = "camelCase")]
1165pub struct StreamAccountConfig {
1166 pub include_owned_accounts: Option<bool>,
1167}
1168
1169impl Default for StreamAccountConfig {
1170 fn default() -> Self {
1171 Self {
1172 include_owned_accounts: Some(false),
1173 }
1174 }
1175}
1176
1177#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1178#[serde(rename_all = "camelCase")]
1179pub struct StreamAccountsEntry {
1180 pub pubkey: String,
1181 #[serde(default)]
1182 pub include_owned_accounts: Option<bool>,
1183}
1184
1185#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1186#[serde(rename_all = "camelCase")]
1187pub struct OfflineAccountConfig {
1188 pub include_owned_accounts: Option<bool>,
1189}
1190
1191impl Default for OfflineAccountConfig {
1192 fn default() -> Self {
1193 Self {
1194 include_owned_accounts: Some(false),
1195 }
1196 }
1197}
1198
1199#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1200#[serde(rename_all = "camelCase")]
1201pub struct StreamedAccountInfo {
1202 pub pubkey: String,
1203 pub include_owned_accounts: bool,
1204}
1205
1206#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1207#[serde(rename_all = "camelCase")]
1208pub struct GetSurfnetInfoResponse {
1209 runbook_executions: Vec<RunbookExecutionStatusReport>,
1210}
1211impl GetSurfnetInfoResponse {
1212 pub fn new(runbook_executions: Vec<RunbookExecutionStatusReport>) -> Self {
1213 Self { runbook_executions }
1214 }
1215}
1216
1217#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1218#[serde(rename_all = "camelCase")]
1219pub struct GetStreamedAccountsResponse {
1220 accounts: Vec<StreamedAccountInfo>,
1221}
1222impl GetStreamedAccountsResponse {
1223 pub fn from_iter<I>(streamed_accounts: I) -> Self
1224 where
1225 I: IntoIterator<Item = (String, bool)>,
1226 {
1227 let accounts = streamed_accounts
1228 .into_iter()
1229 .map(|(pubkey, include_owned_accounts)| StreamedAccountInfo {
1230 pubkey,
1231 include_owned_accounts,
1232 })
1233 .collect();
1234 Self { accounts }
1235 }
1236}
1237
1238#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1239#[serde(rename_all = "camelCase")]
1240pub struct RunbookExecutionStatusReport {
1241 pub started_at: u32,
1242 pub completed_at: Option<u32>,
1243 pub runbook_id: String,
1244 pub errors: Option<Vec<String>>,
1245}
1246impl RunbookExecutionStatusReport {
1247 pub fn new(runbook_id: String) -> Self {
1248 Self {
1249 started_at: Local::now().timestamp() as u32,
1250 completed_at: None,
1251 runbook_id,
1252 errors: None,
1253 }
1254 }
1255 pub fn mark_completed(&mut self, error: Option<Vec<String>>) {
1256 self.completed_at = Some(Local::now().timestamp() as u32);
1257 self.errors = error;
1258 }
1259}
1260#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
1262pub struct WsSubscriptions {
1263 pub signatures: usize,
1264 pub accounts: usize,
1265 pub slots: usize,
1266 pub logs: usize,
1267}
1268
1269#[derive(Debug, Clone, Serialize, Deserialize)]
1271pub struct SurfpoolStatus {
1272 pub slot: u64,
1273 pub epoch: u64,
1274 pub slot_index: u64,
1275 pub transactions_count: u64,
1276 pub transactions_processed: u64,
1277 pub uptime_ms: u64,
1278 pub ws_subscriptions: WsSubscriptions,
1279}
1280
1281#[cfg(feature = "prometheus")]
1282fn default_prometheus_addr() -> String {
1283 "0.0.0.0:9000".to_string()
1284}
1285
1286#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1287#[serde(rename_all = "camelCase")]
1288pub struct CheatcodeConfig {
1289 pub lockout: bool, pub filter: CheatcodeFilter,
1291}
1292
1293#[derive(Serialize, Deserialize, Default)]
1294pub struct CheatcodeControlConfig {
1295 pub lockout: Option<bool>,
1296}
1297
1298#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1299#[serde(untagged)]
1300pub enum CheatcodeFilter {
1301 All(String),
1302 List(Vec<String>), }
1304
1305impl CheatcodeConfig {
1306 pub fn new() -> Arc<Mutex<Self>> {
1307 Arc::new(Mutex::new(CheatcodeConfig {
1308 lockout: false,
1309 filter: CheatcodeFilter::List(vec![]),
1310 }))
1311 }
1312
1313 pub fn lockout(&mut self) {
1314 self.lockout = true;
1315 }
1316
1317 pub fn disable_all(&mut self, lockout: bool, available_cheatcodes: Vec<String>) {
1318 if lockout {
1319 self.lockout = true;
1320 }
1321 self.filter = Self::filter_all_list(lockout, available_cheatcodes);
1322 }
1323
1324 pub fn disable_cheatcode(&mut self, cheatcode: &String) -> Result<(), String> {
1325 if !self.lockout
1326 && (cheatcode.eq("surfnet_enableCheatcode") || cheatcode.eq("surfnet_disableCheatcode"))
1327 {
1328 return Err("Cannot disable surfnet_disableCheatcode or surfnet_enableCheatcode while lockout is false".to_string());
1329 }
1330
1331 if let CheatcodeFilter::List(list) = &mut self.filter {
1332 if !list.contains(cheatcode) {
1333 list.push(cheatcode.to_string());
1334 Ok(())
1335 } else {
1336 Err("Cheatcode already disabled".to_string())
1337 }
1338 } else {
1339 Err("All cheatcodes disabled".to_string())
1340 }
1341 }
1342 pub fn enable_cheatcode(&mut self, cheatcode: &str) -> Result<(), String> {
1343 if let CheatcodeFilter::List(list) = &mut self.filter {
1344 if let Some(pos) = list.iter().position(|c| c == cheatcode) {
1345 list.remove(pos);
1346 Ok(())
1347 } else {
1348 Err("Cheatcode isn't disabled".to_string())
1349 }
1350 } else {
1351 Err("All cheatcodes are disabled".to_string())
1352 }
1353 }
1354
1355 pub fn is_cheatcode_disabled(&self, cheatcode: &String) -> bool {
1356 match &self.filter {
1357 CheatcodeFilter::List(list) => list.contains(cheatcode),
1358 CheatcodeFilter::All(_) => true,
1359 }
1360 }
1361
1362 pub fn filter_all_list(lockout: bool, available_cheatcodes: Vec<String>) -> CheatcodeFilter {
1363 if lockout {
1366 CheatcodeFilter::All("all".to_string())
1367 } else {
1368 let filter = available_cheatcodes
1370 .into_iter()
1371 .filter(|c| c.ne("surfnet_disableCheatcode") && c.ne("surfnet_enableCheatcode"))
1372 .collect();
1373 CheatcodeFilter::List(filter)
1374 }
1375 }
1376}
1377
1378#[cfg(test)]
1379mod tests {
1380 use serde_json::json;
1381 use solana_account_decoder_client_types::{ParsedAccount, UiAccountData};
1382
1383 use super::*;
1384
1385 #[test]
1386 fn test_disable_cheatcode_with_lockout_allows_protected_methods() {
1387 let config = CheatcodeConfig::new();
1392 let mut config = config.lock().unwrap();
1393
1394 config.lockout();
1396
1397 assert!(
1399 config
1400 .disable_cheatcode(&"surfnet_setAccount".to_string())
1401 .is_ok()
1402 );
1403 assert!(
1404 config
1405 .disable_cheatcode(&"surfnet_enableCheatcode".to_string())
1406 .is_ok()
1407 );
1408 assert!(
1409 config
1410 .disable_cheatcode(&"surfnet_disableCheatcode".to_string())
1411 .is_ok()
1412 );
1413 }
1414
1415 #[test]
1416 fn test_disable_cheatcode_without_lockout_rejects_protected_methods() {
1417 let config = CheatcodeConfig::new();
1418 let mut config = config.lock().unwrap();
1419
1420 assert!(
1422 config
1423 .disable_cheatcode(&"surfnet_enableCheatcode".to_string())
1424 .is_err()
1425 );
1426 assert!(
1427 config
1428 .disable_cheatcode(&"surfnet_disableCheatcode".to_string())
1429 .is_err()
1430 );
1431
1432 assert!(
1434 config
1435 .disable_cheatcode(&"surfnet_setAccount".to_string())
1436 .is_ok()
1437 );
1438 }
1439
1440 #[test]
1441 fn test_disable_all_with_lockout_persists_lockout_flag() {
1442 let config = CheatcodeConfig::new();
1445 let mut config = config.lock().unwrap();
1446
1447 let available = vec![
1448 "surfnet_setAccount".to_string(),
1449 "surfnet_enableCheatcode".to_string(),
1450 "surfnet_disableCheatcode".to_string(),
1451 ];
1452
1453 config.disable_all(true, available);
1454 assert!(config.lockout);
1455 }
1456
1457 #[test]
1458 fn test_disable_all_without_lockout_does_not_set_lockout() {
1459 let config = CheatcodeConfig::new();
1460 let mut config = config.lock().unwrap();
1461
1462 let available = vec![
1463 "surfnet_setAccount".to_string(),
1464 "surfnet_enableCheatcode".to_string(),
1465 "surfnet_disableCheatcode".to_string(),
1466 ];
1467
1468 config.disable_all(false, available);
1469 assert!(!config.lockout);
1470 }
1471
1472 #[test]
1473 fn print_ui_keyed_profile_result() {
1474 let pubkey = Pubkey::new_unique();
1475 let owner = Pubkey::new_unique();
1476 let readonly_account_state = UiAccount {
1477 lamports: 100,
1478 data: UiAccountData::Binary(
1479 "ABCDEFG".into(),
1480 solana_account_decoder_client_types::UiAccountEncoding::Base64,
1481 ),
1482 owner: owner.to_string(),
1483 executable: false,
1484 rent_epoch: 0,
1485 space: Some(100),
1486 };
1487
1488 let account_1 = UiAccount {
1489 lamports: 100,
1490 data: UiAccountData::Json(ParsedAccount {
1491 program: "custom-program".into(),
1492 parsed: json!({
1493 "field1": "value1",
1494 "field2": "value2"
1495 }),
1496 space: 50,
1497 }),
1498 owner: owner.to_string(),
1499 executable: false,
1500 rent_epoch: 0,
1501 space: Some(100),
1502 };
1503
1504 let account_2 = UiAccount {
1505 lamports: 100,
1506 data: UiAccountData::Json(ParsedAccount {
1507 program: "custom-program".into(),
1508 parsed: json!({
1509 "field1": "updated-value1",
1510 "field2": "updated-value2"
1511 }),
1512 space: 50,
1513 }),
1514 owner: owner.to_string(),
1515 executable: false,
1516 rent_epoch: 0,
1517 space: Some(100),
1518 };
1519 let profile_result = UiKeyedProfileResult {
1520 slot: 123,
1521 key: UuidOrSignature::Uuid(Uuid::new_v4()),
1522 instruction_profiles: Some(vec![
1523 UiProfileResult {
1524 account_states: IndexMap::from_iter([
1525 (
1526 pubkey,
1527 UiAccountProfileState::Writable(UiAccountChange::Create(
1528 account_1.clone(),
1529 )),
1530 ),
1531 (owner, UiAccountProfileState::Readonly),
1532 ]),
1533 compute_units_consumed: 100,
1534 log_messages: Some(vec![
1535 "Log message: Creating Account".to_string(),
1536 "Log message: Account created".to_string(),
1537 ]),
1538 error_message: None,
1539 },
1540 UiProfileResult {
1541 account_states: IndexMap::from_iter([
1542 (
1543 pubkey,
1544 UiAccountProfileState::Writable(UiAccountChange::Update(
1545 account_1,
1546 account_2.clone(),
1547 )),
1548 ),
1549 (owner, UiAccountProfileState::Readonly),
1550 ]),
1551 compute_units_consumed: 100,
1552 log_messages: Some(vec![
1553 "Log message: Updating Account".to_string(),
1554 "Log message: Account updated".to_string(),
1555 ]),
1556 error_message: None,
1557 },
1558 UiProfileResult {
1559 account_states: IndexMap::from_iter([
1560 (
1561 pubkey,
1562 UiAccountProfileState::Writable(UiAccountChange::Delete(account_2)),
1563 ),
1564 (owner, UiAccountProfileState::Readonly),
1565 ]),
1566 compute_units_consumed: 100,
1567 log_messages: Some(vec![
1568 "Log message: Deleting Account".to_string(),
1569 "Log message: Account deleted".to_string(),
1570 ]),
1571 error_message: None,
1572 },
1573 ]),
1574 transaction_profile: UiProfileResult {
1575 account_states: IndexMap::from_iter([
1576 (
1577 pubkey,
1578 UiAccountProfileState::Writable(UiAccountChange::Unchanged(None)),
1579 ),
1580 (owner, UiAccountProfileState::Readonly),
1581 ]),
1582 compute_units_consumed: 300,
1583 log_messages: Some(vec![
1584 "Log message: Creating Account".to_string(),
1585 "Log message: Account created".to_string(),
1586 "Log message: Updating Account".to_string(),
1587 "Log message: Account updated".to_string(),
1588 "Log message: Deleting Account".to_string(),
1589 "Log message: Account deleted".to_string(),
1590 ]),
1591 error_message: None,
1592 },
1593 readonly_account_states: IndexMap::from_iter([(owner, readonly_account_state)]),
1594 };
1595 println!("{}", serde_json::to_string_pretty(&profile_result).unwrap());
1596 }
1597
1598 #[test]
1599 fn test_profiling_map_capacity() {
1600 let profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1601 assert_eq!(profiling_map.capacity(), 10);
1602 }
1603
1604 #[test]
1605 fn test_profiling_map_len() {
1606 let profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1607 assert!(profiling_map.len() == 0);
1608 }
1609
1610 #[test]
1611 fn test_profiling_map_is_empty() {
1612 let profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1613 assert_eq!(profiling_map.is_empty(), true);
1614 }
1615
1616 #[test]
1617 fn test_profiling_map_insert() {
1618 let mut profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1619 let key = Signature::default();
1620 let value = KeyedProfileResult::new(
1621 1,
1622 UuidOrSignature::Signature(key),
1623 None,
1624 ProfileResult::new(BTreeMap::new(), BTreeMap::new(), 0, None, None),
1625 HashMap::new(),
1626 );
1627 profiling_map.insert(key, value.clone());
1628 assert_eq!(profiling_map.len(), 1);
1629 }
1630
1631 #[test]
1632 fn test_profiling_map_get() {
1633 let mut profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1634 let key = Signature::default();
1635 let value = KeyedProfileResult::new(
1636 1,
1637 UuidOrSignature::Signature(key),
1638 None,
1639 ProfileResult::new(BTreeMap::new(), BTreeMap::new(), 0, None, None),
1640 HashMap::new(),
1641 );
1642 profiling_map.insert(key, value.clone());
1643
1644 assert_eq!(profiling_map.get(&key), Some(&value));
1645 }
1646
1647 #[test]
1648 fn test_profiling_map_get_mut() {
1649 let mut profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1650 let key = Signature::default();
1651 let mut value = KeyedProfileResult::new(
1652 1,
1653 UuidOrSignature::Signature(key),
1654 None,
1655 ProfileResult::new(BTreeMap::new(), BTreeMap::new(), 0, None, None),
1656 HashMap::new(),
1657 );
1658 profiling_map.insert(key, value.clone());
1659 assert_eq!(profiling_map.get_mut(&key), Some(&mut value));
1660 }
1661
1662 #[test]
1663 fn test_profiling_map_contains_key() {
1664 let mut profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1665 let key = Signature::default();
1666 let value = KeyedProfileResult::new(
1667 1,
1668 UuidOrSignature::Signature(key),
1669 None,
1670 ProfileResult::new(BTreeMap::new(), BTreeMap::new(), 0, None, None),
1671 HashMap::new(),
1672 );
1673 profiling_map.insert(key, value.clone());
1674
1675 assert_eq!(profiling_map.contains_key(&key), true);
1676 }
1677
1678 #[test]
1679 fn test_profiling_map_iter() {
1680 let mut profiling_map = FifoMap::<Signature, KeyedProfileResult>::new(10);
1681 let key = Signature::default();
1682 let value = KeyedProfileResult::new(
1683 1,
1684 UuidOrSignature::Signature(key),
1685 None,
1686 ProfileResult::new(BTreeMap::new(), BTreeMap::new(), 0, None, None),
1687 HashMap::new(),
1688 );
1689 profiling_map.insert(key, value.clone());
1690
1691 assert_eq!(profiling_map.iter().count(), 1);
1692 }
1693
1694 #[test]
1695 fn test_profiling_map_evicts_oldest_on_overflow() {
1696 let mut profiling_map = FifoMap::<String, u32>::new(10);
1697 profiling_map.insert("a".to_string(), 1);
1698 profiling_map.insert("b".to_string(), 2);
1699 profiling_map.insert("c".to_string(), 3);
1700 profiling_map.insert("d".to_string(), 4);
1701 profiling_map.insert("e".to_string(), 5);
1702 profiling_map.insert("f".to_string(), 6);
1703 profiling_map.insert("g".to_string(), 7);
1704 profiling_map.insert("h".to_string(), 8);
1705 profiling_map.insert("i".to_string(), 9);
1706 profiling_map.insert("j".to_string(), 10);
1707
1708 println!("Profiling map: {:?}", profiling_map);
1709 println!("Profile Map capacity: {:?}", profiling_map.capacity());
1710 println!("Profile Map len: {:?}", profiling_map.len());
1711
1712 assert_eq!(profiling_map.len(), 10);
1713
1714 profiling_map.insert("k".to_string(), 11);
1716 assert_eq!(profiling_map.len(), 10);
1717 assert_eq!(profiling_map.get(&"a".to_string()), None);
1718 assert_eq!(profiling_map.get(&"k".to_string()), Some(&11));
1719 }
1720
1721 #[test]
1722 fn test_profiling_map_update_do_not_reorder() {
1723 let mut profiling_map = FifoMap::<&str, u32>::new(4);
1724 profiling_map.insert("a", 1);
1725 profiling_map.insert("b", 2);
1726 profiling_map.insert("c", 3);
1727 profiling_map.insert("d", 4);
1728
1729 println!("Profiling map: {:?}", profiling_map);
1731 println!("Profile Map key b holds: {:?}", profiling_map.get(&"b"));
1732 profiling_map.insert("b", 4);
1733 println!("Profile Map key b holds: {:?}", profiling_map.get(&"b"));
1734
1735 profiling_map.insert("e", 5);
1737 assert_eq!(profiling_map.len(), 4);
1738 assert_eq!(profiling_map.get(&"a"), None);
1739 assert_eq!(profiling_map.get(&"b"), Some(&4));
1740 assert_eq!(profiling_map.get(&"e"), Some(&5));
1741
1742 let get: Vec<_> = profiling_map.iter().map(|(k, v)| (*k, *v)).collect();
1743 println!("Profiling map: {:?}", get);
1744 assert_eq!(get, vec![("b", 4), ("c", 3), ("d", 4), ("e", 5)]);
1745 }
1746
1747 #[test]
1748 fn test_export_snapshot_scope_serialization() {
1749 let network_config = ExportSnapshotConfig {
1751 include_parsed_accounts: None,
1752 filter: None,
1753 scope: ExportSnapshotScope::Network,
1754 };
1755 let network_json = serde_json::to_value(&network_config).unwrap();
1756 println!(
1757 "Network config: {}",
1758 serde_json::to_string_pretty(&network_json).unwrap()
1759 );
1760 assert_eq!(network_json["scope"], json!("network"));
1761
1762 let pre_tx_config = ExportSnapshotConfig {
1764 include_parsed_accounts: None,
1765 filter: None,
1766 scope: ExportSnapshotScope::PreTransaction("5signature123".to_string()),
1767 };
1768 let pre_tx_json = serde_json::to_value(&pre_tx_config).unwrap();
1769 println!(
1770 "PreTransaction config: {}",
1771 serde_json::to_string_pretty(&pre_tx_json).unwrap()
1772 );
1773 assert_eq!(
1774 pre_tx_json["scope"],
1775 json!({"preTransaction": "5signature123"})
1776 );
1777
1778 let deserialized_network: ExportSnapshotConfig =
1780 serde_json::from_value(network_json).unwrap();
1781 assert_eq!(deserialized_network.scope, ExportSnapshotScope::Network);
1782
1783 let deserialized_pre_tx: ExportSnapshotConfig =
1784 serde_json::from_value(pre_tx_json).unwrap();
1785 assert_eq!(
1786 deserialized_pre_tx.scope,
1787 ExportSnapshotScope::PreTransaction("5signature123".to_string())
1788 );
1789 }
1790
1791 #[test]
1792 fn test_sanitize_datasource_url_strips_path_and_query() {
1793 let config = SimnetConfig {
1795 remote_rpc_url: Some(
1796 "https://example.rpc-provider.com/v2/abc123def456ghi789".to_string(),
1797 ),
1798 ..Default::default()
1799 };
1800 let sanitized = config.get_sanitized_datasource_url().unwrap();
1801 assert_eq!(sanitized, "https://example.rpc-provider.com");
1802 assert!(!sanitized.contains("abc123"));
1803 }
1804
1805 #[test]
1806 fn test_sanitize_datasource_url_strips_query_params() {
1807 let config = SimnetConfig {
1808 remote_rpc_url: Some(
1809 "https://mainnet.helius-rpc.com/?api-key=secret-key-12345".to_string(),
1810 ),
1811 ..Default::default()
1812 };
1813 let sanitized = config.get_sanitized_datasource_url().unwrap();
1814 assert_eq!(sanitized, "https://mainnet.helius-rpc.com");
1815 assert!(!sanitized.contains("secret-key"));
1816 }
1817
1818 #[test]
1819 fn test_sanitize_datasource_url_public_rpc() {
1820 let config = SimnetConfig {
1821 remote_rpc_url: Some("https://api.mainnet-beta.solana.com".to_string()),
1822 ..Default::default()
1823 };
1824 let sanitized = config.get_sanitized_datasource_url().unwrap();
1825 assert_eq!(sanitized, "https://api.mainnet-beta.solana.com");
1826 }
1827
1828 #[test]
1829 fn test_sanitize_datasource_url_none() {
1830 let config = SimnetConfig {
1831 remote_rpc_url: None,
1832 ..Default::default()
1833 };
1834 assert!(config.get_sanitized_datasource_url().is_none());
1835 }
1836
1837 #[test]
1838 fn test_sanitize_datasource_url_invalid() {
1839 let config = SimnetConfig {
1840 remote_rpc_url: Some("not-a-valid-url".to_string()),
1841 ..Default::default()
1842 };
1843 assert!(config.get_sanitized_datasource_url().is_none());
1844 }
1845
1846 #[test]
1847 fn test_simnet_config_skip_blockhash_check_defaults_on_deserialize() {
1848 let mut config_json = serde_json::to_value(SimnetConfig::default()).unwrap();
1849 config_json
1850 .as_object_mut()
1851 .unwrap()
1852 .remove("skip_blockhash_check");
1853
1854 let config: SimnetConfig = serde_json::from_value(config_json).unwrap();
1855 assert!(!config.skip_blockhash_check);
1856 }
1857}