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