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};
14use serde_with::{BytesOrString, serde_as};
15use solana_account::Account;
16use solana_account_decoder_client_types::{UiAccount, UiAccountEncoding};
17use solana_clock::{Clock, Epoch, Slot};
18use solana_epoch_info::EpochInfo;
19use solana_message::inner_instruction::InnerInstructionsList;
20use solana_pubkey::Pubkey;
21use solana_signature::Signature;
22use solana_transaction::versioned::VersionedTransaction;
23use solana_transaction_context::TransactionReturnData;
24use solana_transaction_error::TransactionError;
25use txtx_addon_kit::indexmap::IndexMap;
26use txtx_addon_network_svm_types::subgraph::SubgraphRequest;
27use uuid::Uuid;
28
29pub const DEFAULT_RPC_URL: &str = "https://api.mainnet-beta.solana.com";
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;
37
38#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
39pub struct TransactionMetadata {
40 pub signature: Signature,
41 pub logs: Vec<String>,
42 pub inner_instructions: InnerInstructionsList,
43 pub compute_units_consumed: u64,
44 pub return_data: TransactionReturnData,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub enum TransactionConfirmationStatus {
50 Processed,
51 Confirmed,
52 Finalized,
53}
54
55#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
56pub enum BlockProductionMode {
57 #[default]
58 Clock,
59 Transaction,
60 Manual,
61}
62
63#[derive(Debug)]
64pub enum SubgraphEvent {
65 EndpointReady,
66 InfoLog(DateTime<Local>, String),
67 ErrorLog(DateTime<Local>, String),
68 WarnLog(DateTime<Local>, String),
69 DebugLog(DateTime<Local>, String),
70 Shutdown,
71}
72
73impl SubgraphEvent {
74 pub fn info<S>(msg: S) -> Self
75 where
76 S: Into<String>,
77 {
78 Self::InfoLog(Local::now(), msg.into())
79 }
80
81 pub fn warn<S>(msg: S) -> Self
82 where
83 S: Into<String>,
84 {
85 Self::WarnLog(Local::now(), msg.into())
86 }
87
88 pub fn error<S>(msg: S) -> Self
89 where
90 S: Into<String>,
91 {
92 Self::ErrorLog(Local::now(), msg.into())
93 }
94
95 pub fn debug<S>(msg: S) -> Self
96 where
97 S: Into<String>,
98 {
99 Self::DebugLog(Local::now(), msg.into())
100 }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
105#[serde(rename_all = "camelCase")]
106pub struct ComputeUnitsEstimationResult {
107 pub success: bool,
108 pub compute_units_consumed: u64,
109 pub log_messages: Option<Vec<String>>,
110 pub error_message: Option<String>,
111}
112
113#[derive(Debug, Clone, PartialEq)]
115pub struct KeyedProfileResult {
116 pub slot: u64,
117 pub key: UuidOrSignature,
118 pub instruction_profiles: Option<Vec<ProfileResult>>,
119 pub transaction_profile: ProfileResult,
120 pub readonly_account_states: HashMap<Pubkey, Account>,
121}
122
123impl KeyedProfileResult {
124 pub fn new(
125 slot: u64,
126 key: UuidOrSignature,
127 instruction_profiles: Option<Vec<ProfileResult>>,
128 transaction_profile: ProfileResult,
129 readonly_account_states: HashMap<Pubkey, Account>,
130 ) -> Self {
131 Self {
132 slot,
133 key,
134 instruction_profiles,
135 transaction_profile,
136 readonly_account_states,
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq)]
142pub struct ProfileResult {
143 pub pre_execution_capture: ExecutionCapture,
144 pub post_execution_capture: ExecutionCapture,
145 pub compute_units_consumed: u64,
146 pub log_messages: Option<Vec<String>>,
147 pub error_message: Option<String>,
148}
149
150pub type ExecutionCapture = BTreeMap<Pubkey, Option<Account>>;
151
152impl ProfileResult {
153 pub fn new(
154 pre_execution_capture: ExecutionCapture,
155 post_execution_capture: ExecutionCapture,
156 compute_units_consumed: u64,
157 log_messages: Option<Vec<String>>,
158 error_message: Option<String>,
159 ) -> Self {
160 Self {
161 pre_execution_capture,
162 post_execution_capture,
163 compute_units_consumed,
164 log_messages,
165 error_message,
166 }
167 }
168}
169
170#[derive(Debug, Clone, PartialEq)]
171pub enum AccountProfileState {
172 Readonly,
173 Writable(AccountChange),
174}
175
176impl AccountProfileState {
177 pub fn new(
178 pubkey: Pubkey,
179 pre_account: Option<Account>,
180 post_account: Option<Account>,
181 readonly_accounts: &[Pubkey],
182 ) -> Self {
183 if readonly_accounts.contains(&pubkey) {
184 return AccountProfileState::Readonly;
185 }
186
187 match (pre_account, post_account) {
188 (None, Some(post_account)) => {
189 AccountProfileState::Writable(AccountChange::Create(post_account))
190 }
191 (Some(pre_account), None) => {
192 AccountProfileState::Writable(AccountChange::Delete(pre_account))
193 }
194 (Some(pre_account), Some(post_account)) if pre_account == post_account => {
195 AccountProfileState::Writable(AccountChange::Unchanged(Some(pre_account)))
196 }
197 (Some(pre_account), Some(post_account)) => {
198 AccountProfileState::Writable(AccountChange::Update(pre_account, post_account))
199 }
200 (None, None) => AccountProfileState::Writable(AccountChange::Unchanged(None)),
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq)]
206pub enum AccountChange {
207 Create(Account),
208 Update(Account, Account),
209 Delete(Account),
210 Unchanged(Option<Account>),
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214#[serde(rename_all = "camelCase")]
215pub struct RpcProfileResultConfig {
216 pub encoding: Option<UiAccountEncoding>,
217 pub depth: Option<RpcProfileDepth>,
218}
219
220impl Default for RpcProfileResultConfig {
221 fn default() -> Self {
222 Self {
223 encoding: Some(UiAccountEncoding::JsonParsed),
224 depth: Some(RpcProfileDepth::default()),
225 }
226 }
227}
228
229#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub enum RpcProfileDepth {
232 Transaction,
233 #[default]
234 Instruction,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
238#[serde(rename_all = "camelCase")]
239pub struct UiKeyedProfileResult {
240 pub slot: u64,
241 pub key: UuidOrSignature,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub instruction_profiles: Option<Vec<UiProfileResult>>,
244 pub transaction_profile: UiProfileResult,
245 #[serde(with = "profile_state_map")]
246 pub readonly_account_states: IndexMap<Pubkey, UiAccount>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
250#[serde(rename_all = "camelCase")]
251pub struct UiProfileResult {
252 #[serde(with = "profile_state_map")]
253 pub account_states: IndexMap<Pubkey, UiAccountProfileState>,
254 pub compute_units_consumed: u64,
255 pub log_messages: Option<Vec<String>>,
256 pub error_message: Option<String>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
260#[serde(rename_all = "camelCase", tag = "type", content = "accountChange")]
261pub enum UiAccountProfileState {
262 Readonly,
263 Writable(UiAccountChange),
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
267#[serde(rename_all = "camelCase", tag = "type", content = "data")]
268pub enum UiAccountChange {
269 Create(UiAccount),
270 Update(UiAccount, UiAccount),
271 Delete(UiAccount),
272 Unchanged(Option<UiAccount>),
274}
275
276pub mod profile_state_map {
287 use super::*;
288
289 pub fn serialize<S, T>(map: &IndexMap<Pubkey, T>, serializer: S) -> Result<S::Ok, S::Error>
290 where
291 S: Serializer,
292 T: Serialize,
293 {
294 let str_map: IndexMap<String, &T> = map.iter().map(|(k, v)| (k.to_string(), v)).collect();
295 str_map.serialize(serializer)
296 }
297
298 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<IndexMap<Pubkey, T>, D::Error>
299 where
300 D: Deserializer<'de>,
301 T: Deserialize<'de>,
302 {
303 let str_map: IndexMap<String, T> = IndexMap::deserialize(deserializer)?;
304 str_map
305 .into_iter()
306 .map(|(k, v)| {
307 Pubkey::from_str(&k)
308 .map(|pk| (pk, v))
309 .map_err(serde::de::Error::custom)
310 })
311 .collect()
312 }
313}
314
315#[derive(Debug, Clone)]
316pub enum SubgraphCommand {
317 CreateCollection(Uuid, SubgraphRequest, Sender<String>),
318 ObserveCollection(Receiver<DataIndexingCommand>),
319 Shutdown,
320}
321
322#[derive(Debug)]
323pub enum SimnetEvent {
324 Ready,
325 Connected(String),
326 Aborted(String),
327 Shutdown,
328 SystemClockUpdated(Clock),
329 ClockUpdate(ClockCommand),
330 EpochInfoUpdate(EpochInfo),
331 BlockHashExpired,
332 InfoLog(DateTime<Local>, String),
333 ErrorLog(DateTime<Local>, String),
334 WarnLog(DateTime<Local>, String),
335 DebugLog(DateTime<Local>, String),
336 PluginLoaded(String),
337 TransactionReceived(DateTime<Local>, VersionedTransaction),
338 TransactionProcessed(
339 DateTime<Local>,
340 TransactionMetadata,
341 Option<TransactionError>,
342 ),
343 AccountUpdate(DateTime<Local>, Pubkey),
344 TaggedProfile {
345 result: KeyedProfileResult,
346 tag: String,
347 timestamp: DateTime<Local>,
348 },
349 RunbookStarted(String),
350 RunbookCompleted(String),
351}
352
353impl SimnetEvent {
354 pub fn info<S>(msg: S) -> Self
355 where
356 S: Into<String>,
357 {
358 Self::InfoLog(Local::now(), msg.into())
359 }
360
361 pub fn warn<S>(msg: S) -> Self
362 where
363 S: Into<String>,
364 {
365 Self::WarnLog(Local::now(), msg.into())
366 }
367
368 pub fn error<S>(msg: S) -> Self
369 where
370 S: Into<String>,
371 {
372 Self::ErrorLog(Local::now(), msg.into())
373 }
374
375 pub fn debug<S>(msg: S) -> Self
376 where
377 S: Into<String>,
378 {
379 Self::DebugLog(Local::now(), msg.into())
380 }
381
382 pub fn transaction_processed(meta: TransactionMetadata, err: Option<TransactionError>) -> Self {
383 Self::TransactionProcessed(Local::now(), meta, err)
384 }
385
386 pub fn transaction_received(tx: VersionedTransaction) -> Self {
387 Self::TransactionReceived(Local::now(), tx)
388 }
389
390 pub fn account_update(pubkey: Pubkey) -> Self {
391 Self::AccountUpdate(Local::now(), pubkey)
392 }
393
394 pub fn tagged_profile(result: KeyedProfileResult, tag: String) -> Self {
395 Self::TaggedProfile {
396 result,
397 tag,
398 timestamp: Local::now(),
399 }
400 }
401
402 pub fn account_update_msg(&self) -> String {
403 match self {
404 SimnetEvent::AccountUpdate(_, pubkey) => {
405 format!("Account {} updated.", pubkey)
406 }
407 _ => unreachable!("This function should only be called for AccountUpdate events"),
408 }
409 }
410
411 pub fn epoch_info_update_msg(&self) -> String {
412 match self {
413 SimnetEvent::EpochInfoUpdate(epoch_info) => {
414 format!(
415 "Datasource connection successful. Epoch {} / Slot index {} / Slot {}.",
416 epoch_info.epoch, epoch_info.slot_index, epoch_info.absolute_slot
417 )
418 }
419 _ => unreachable!("This function should only be called for EpochInfoUpdate events"),
420 }
421 }
422
423 pub fn plugin_loaded_msg(&self) -> String {
424 match self {
425 SimnetEvent::PluginLoaded(plugin_name) => {
426 format!("Plugin {} successfully loaded.", plugin_name)
427 }
428 _ => unreachable!("This function should only be called for PluginLoaded events"),
429 }
430 }
431
432 pub fn clock_update_msg(&self) -> String {
433 match self {
434 SimnetEvent::SystemClockUpdated(clock) => {
435 format!("Clock ticking (epoch {}, slot {})", clock.epoch, clock.slot)
436 }
437 _ => {
438 unreachable!("This function should only be called for SystemClockUpdated events")
439 }
440 }
441 }
442}
443
444#[derive(Debug)]
445pub enum TransactionStatusEvent {
446 Success(TransactionConfirmationStatus),
447 SimulationFailure((TransactionError, TransactionMetadata)),
448 ExecutionFailure((TransactionError, TransactionMetadata)),
449 VerificationFailure(String),
450}
451
452#[derive(Debug)]
453pub enum SimnetCommand {
454 SlotForward(Option<Hash>),
455 SlotBackward(Option<Hash>),
456 CommandClock(ClockCommand),
457 UpdateInternalClock(Clock),
458 UpdateBlockProductionMode(BlockProductionMode),
459 TransactionReceived(
460 Option<Hash>,
461 VersionedTransaction,
462 Sender<TransactionStatusEvent>,
463 bool,
464 ),
465 Terminate(Option<Hash>),
466}
467
468#[derive(Debug)]
469pub enum ClockCommand {
470 Pause,
471 Resume,
472 Toggle,
473 UpdateSlotInterval(u64),
474}
475
476pub enum ClockEvent {
477 Tick,
478 ExpireBlockHash,
479}
480
481#[derive(Clone, Debug, Default, Serialize)]
482pub struct SanitizedConfig {
483 pub rpc_url: String,
484 pub ws_url: String,
485 pub rpc_datasource_url: String,
486 pub studio_url: String,
487 pub graphql_query_route_url: String,
488 pub version: String,
489 pub workspace: Option<String>,
490}
491
492#[derive(Clone, Debug, Default)]
493pub struct SurfpoolConfig {
494 pub simnets: Vec<SimnetConfig>,
495 pub rpc: RpcConfig,
496 pub subgraph: SubgraphConfig,
497 pub studio: StudioConfig,
498 pub plugin_config_path: Vec<PathBuf>,
499}
500
501#[derive(Clone, Debug)]
502pub struct SimnetConfig {
503 pub remote_rpc_url: String,
504 pub slot_time: u64,
505 pub block_production_mode: BlockProductionMode,
506 pub airdrop_addresses: Vec<Pubkey>,
507 pub airdrop_token_amount: u64,
508 pub expiry: Option<u64>,
509}
510
511impl Default for SimnetConfig {
512 fn default() -> Self {
513 Self {
514 remote_rpc_url: DEFAULT_RPC_URL.to_string(),
515 slot_time: DEFAULT_SLOT_TIME_MS, block_production_mode: BlockProductionMode::Clock,
517 airdrop_addresses: vec![],
518 airdrop_token_amount: 0,
519 expiry: None,
520 }
521 }
522}
523
524impl SimnetConfig {
525 pub fn get_sanitized_datasource_url(&self) -> String {
526 self.remote_rpc_url
527 .split("?")
528 .map(|e| e.to_string())
529 .collect::<Vec<String>>()
530 .first()
531 .expect("datasource url invalid")
532 .to_string()
533 }
534}
535
536#[derive(Clone, Debug, Default)]
537pub struct SubgraphConfig {}
538
539#[derive(Clone, Debug)]
540pub struct RpcConfig {
541 pub bind_host: String,
542 pub bind_port: u16,
543 pub ws_port: u16,
544}
545
546impl RpcConfig {
547 pub fn get_rpc_base_url(&self) -> String {
548 format!("{}:{}", self.bind_host, self.bind_port)
549 }
550 pub fn get_ws_base_url(&self) -> String {
551 format!("{}:{}", self.bind_host, self.ws_port)
552 }
553}
554
555impl Default for RpcConfig {
556 fn default() -> Self {
557 Self {
558 bind_host: DEFAULT_NETWORK_HOST.to_string(),
559 bind_port: DEFAULT_RPC_PORT,
560 ws_port: DEFAULT_WS_PORT,
561 }
562 }
563}
564
565#[derive(Clone, Debug)]
566pub struct StudioConfig {
567 pub bind_host: String,
568 pub bind_port: u16,
569}
570
571impl StudioConfig {
572 pub fn get_studio_base_url(&self) -> String {
573 format!("{}:{}", self.bind_host, self.bind_port)
574 }
575}
576
577impl Default for StudioConfig {
578 fn default() -> Self {
579 Self {
580 bind_host: DEFAULT_NETWORK_HOST.to_string(),
581 bind_port: CHANGE_TO_DEFAULT_STUDIO_PORT_ONCE_SUPERVISOR_MERGED,
582 }
583 }
584}
585
586#[derive(Debug, Clone, Deserialize, Serialize)]
587pub struct SubgraphPluginConfig {
588 pub uuid: Uuid,
589 pub ipc_token: String,
590 pub subgraph_request: SubgraphRequest,
591}
592
593#[derive(Serialize, Deserialize, Clone, Debug)]
594pub struct SvmSimnetInitializationRequest {
595 pub domain: String,
596 pub block_production_mode: BlockProductionMode,
597 pub datasource_rpc_url: String,
598}
599
600#[derive(Serialize, Deserialize, Clone, Debug)]
601pub enum SvmSimnetCommand {
602 Init(SvmSimnetInitializationRequest),
603}
604
605#[derive(Serialize, Deserialize)]
606pub struct CreateNetworkRequest {
607 pub workspace_id: Uuid,
608 pub name: String,
609 pub description: Option<String>,
610 pub datasource_rpc_url: String,
611 pub block_production_mode: BlockProductionMode,
612}
613
614impl CreateNetworkRequest {
615 pub fn new(
616 workspace_id: Uuid,
617 name: String,
618 description: Option<String>,
619 datasource_rpc_url: String,
620 block_production_mode: BlockProductionMode,
621 ) -> Self {
622 Self {
623 workspace_id,
624 name,
625 description,
626 datasource_rpc_url,
627 block_production_mode,
628 }
629 }
630}
631
632#[derive(Serialize, Deserialize)]
633pub struct CreateNetworkResponse {
634 pub rpc_url: String,
635}
636
637#[derive(Serialize, Deserialize)]
638pub struct DeleteNetworkRequest {
639 pub workspace_id: Uuid,
640 pub network_id: Uuid,
641}
642
643impl DeleteNetworkRequest {
644 pub fn new(workspace_id: Uuid, network_id: Uuid) -> Self {
645 Self {
646 workspace_id,
647 network_id,
648 }
649 }
650}
651
652#[derive(Serialize, Deserialize)]
653pub struct DeleteNetworkResponse;
654
655#[serde_as]
656#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
657#[serde(rename_all = "camelCase")]
658pub struct AccountUpdate {
659 pub lamports: Option<u64>,
661 #[serde_as(as = "Option<BytesOrString>")]
663 pub data: Option<Vec<u8>>,
664 pub owner: Option<String>,
666 pub executable: Option<bool>,
668 pub rent_epoch: Option<Epoch>,
670}
671
672#[derive(Debug, Clone)]
673pub enum SetSomeAccount {
674 Account(String),
675 NoAccount,
676}
677
678impl<'de> Deserialize<'de> for SetSomeAccount {
679 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
680 where
681 D: Deserializer<'de>,
682 {
683 struct SetSomeAccountVisitor;
684
685 impl<'de> Visitor<'de> for SetSomeAccountVisitor {
686 type Value = SetSomeAccount;
687
688 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
689 formatter.write_str("a Pubkey String or the String 'null'")
690 }
691
692 fn visit_some<D_>(self, deserializer: D_) -> std::result::Result<Self::Value, D_::Error>
693 where
694 D_: Deserializer<'de>,
695 {
696 Deserialize::deserialize(deserializer).map(|v: String| match v.as_str() {
697 "null" => SetSomeAccount::NoAccount,
698 _ => SetSomeAccount::Account(v.to_string()),
699 })
700 }
701 }
702
703 deserializer.deserialize_option(SetSomeAccountVisitor)
704 }
705}
706
707impl Serialize for SetSomeAccount {
708 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
709 where
710 S: Serializer,
711 {
712 match self {
713 SetSomeAccount::Account(val) => serializer.serialize_str(val),
714 SetSomeAccount::NoAccount => serializer.serialize_str("null"),
715 }
716 }
717}
718
719#[serde_as]
720#[derive(Debug, Clone, Default, Serialize, Deserialize)]
721#[serde(rename_all = "camelCase")]
722pub struct TokenAccountUpdate {
723 pub amount: Option<u64>,
725 pub delegate: Option<SetSomeAccount>,
727 pub state: Option<String>,
729 pub delegated_amount: Option<u64>,
731 pub close_authority: Option<SetSomeAccount>,
733}
734
735#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
737pub struct SupplyUpdate {
738 pub total: Option<u64>,
739 pub circulating: Option<u64>,
740 pub non_circulating: Option<u64>,
741 pub non_circulating_accounts: Option<Vec<String>>,
742}
743
744#[derive(Clone, Debug, PartialEq, Copy)]
745pub enum UuidOrSignature {
746 Uuid(Uuid),
747 Signature(Signature),
748}
749
750impl std::fmt::Display for UuidOrSignature {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 match self {
753 UuidOrSignature::Uuid(uuid) => write!(f, "{}", uuid),
754 UuidOrSignature::Signature(signature) => write!(f, "{}", signature),
755 }
756 }
757}
758
759impl<'de> Deserialize<'de> for UuidOrSignature {
760 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
761 where
762 D: Deserializer<'de>,
763 {
764 let s = String::deserialize(deserializer)?;
765
766 if let Ok(uuid) = Uuid::parse_str(&s) {
767 return Ok(UuidOrSignature::Uuid(uuid));
768 }
769
770 if let Ok(signature) = s.parse::<Signature>() {
771 return Ok(UuidOrSignature::Signature(signature));
772 }
773
774 Err(serde::de::Error::custom(
775 "expected a Uuid or a valid Solana Signature",
776 ))
777 }
778}
779
780impl<'de> Serialize for UuidOrSignature {
781 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
782 where
783 S: Serializer,
784 {
785 match self {
786 UuidOrSignature::Uuid(uuid) => serializer.serialize_str(&uuid.to_string()),
787 UuidOrSignature::Signature(signature) => {
788 serializer.serialize_str(&signature.to_string())
789 }
790 }
791 }
792}
793
794#[derive(Debug, Clone, Deserialize, Serialize)]
795pub enum DataIndexingCommand {
796 ProcessCollection(Uuid),
797 ProcessCollectionEntriesPack(Uuid, Vec<u8>),
798}
799
800#[derive(Debug, Clone)]
802pub struct VersionedIdl(pub Slot, pub Idl);
803
804impl PartialEq for VersionedIdl {
806 fn eq(&self, other: &Self) -> bool {
807 self.0 == other.0
808 }
809}
810
811impl Eq for VersionedIdl {}
812
813impl PartialOrd for VersionedIdl {
814 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
815 Some(self.cmp(other))
816 }
817}
818
819impl Ord for VersionedIdl {
820 fn cmp(&self, other: &Self) -> Ordering {
821 self.0.cmp(&other.0)
822 }
823}
824
825#[cfg(test)]
826mod tests {
827 use serde_json::json;
828 use solana_account_decoder_client_types::{ParsedAccount, UiAccountData};
829
830 use super::*;
831
832 #[test]
833 fn print_ui_keyed_profile_result() {
834 let pubkey = Pubkey::new_unique();
835 let owner = Pubkey::new_unique();
836 let readonly_account_state = UiAccount {
837 lamports: 100,
838 data: UiAccountData::Binary(
839 "ABCDEFG".into(),
840 solana_account_decoder_client_types::UiAccountEncoding::Base64,
841 ),
842 owner: owner.to_string(),
843 executable: false,
844 rent_epoch: 0,
845 space: Some(100),
846 };
847
848 let account_1 = UiAccount {
849 lamports: 100,
850 data: UiAccountData::Json(ParsedAccount {
851 program: "custom-program".into(),
852 parsed: json!({
853 "field1": "value1",
854 "field2": "value2"
855 }),
856 space: 50,
857 }),
858 owner: owner.to_string(),
859 executable: false,
860 rent_epoch: 0,
861 space: Some(100),
862 };
863
864 let account_2 = UiAccount {
865 lamports: 100,
866 data: UiAccountData::Json(ParsedAccount {
867 program: "custom-program".into(),
868 parsed: json!({
869 "field1": "updated-value1",
870 "field2": "updated-value2"
871 }),
872 space: 50,
873 }),
874 owner: owner.to_string(),
875 executable: false,
876 rent_epoch: 0,
877 space: Some(100),
878 };
879 let profile_result = UiKeyedProfileResult {
880 slot: 123,
881 key: UuidOrSignature::Uuid(Uuid::new_v4()),
882 instruction_profiles: Some(vec![
883 UiProfileResult {
884 account_states: IndexMap::from_iter([
885 (
886 pubkey,
887 UiAccountProfileState::Writable(UiAccountChange::Create(
888 account_1.clone(),
889 )),
890 ),
891 (owner, UiAccountProfileState::Readonly),
892 ]),
893 compute_units_consumed: 100,
894 log_messages: Some(vec![
895 "Log message: Creating Account".to_string(),
896 "Log message: Account created".to_string(),
897 ]),
898 error_message: None,
899 },
900 UiProfileResult {
901 account_states: IndexMap::from_iter([
902 (
903 pubkey,
904 UiAccountProfileState::Writable(UiAccountChange::Update(
905 account_1,
906 account_2.clone(),
907 )),
908 ),
909 (owner, UiAccountProfileState::Readonly),
910 ]),
911 compute_units_consumed: 100,
912 log_messages: Some(vec![
913 "Log message: Updating Account".to_string(),
914 "Log message: Account updated".to_string(),
915 ]),
916 error_message: None,
917 },
918 UiProfileResult {
919 account_states: IndexMap::from_iter([
920 (
921 pubkey,
922 UiAccountProfileState::Writable(UiAccountChange::Delete(account_2)),
923 ),
924 (owner, UiAccountProfileState::Readonly),
925 ]),
926 compute_units_consumed: 100,
927 log_messages: Some(vec![
928 "Log message: Deleting Account".to_string(),
929 "Log message: Account deleted".to_string(),
930 ]),
931 error_message: None,
932 },
933 ]),
934 transaction_profile: UiProfileResult {
935 account_states: IndexMap::from_iter([
936 (
937 pubkey,
938 UiAccountProfileState::Writable(UiAccountChange::Unchanged(None)),
939 ),
940 (owner, UiAccountProfileState::Readonly),
941 ]),
942 compute_units_consumed: 300,
943 log_messages: Some(vec![
944 "Log message: Creating Account".to_string(),
945 "Log message: Account created".to_string(),
946 "Log message: Updating Account".to_string(),
947 "Log message: Account updated".to_string(),
948 "Log message: Deleting Account".to_string(),
949 "Log message: Account deleted".to_string(),
950 ]),
951 error_message: None,
952 },
953 readonly_account_states: IndexMap::from_iter([(owner, readonly_account_state)]),
954 };
955 println!("{}", serde_json::to_string_pretty(&profile_result).unwrap());
956 }
957}