surfpool_types/
types.rs

1use std::{collections::BTreeMap, fmt, path::PathBuf, str::FromStr};
2
3use blake3::Hash;
4use chrono::{DateTime, Local};
5use crossbeam_channel::{Receiver, Sender};
6// use litesvm::types::TransactionMetadata;
7use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
8use serde_with::{BytesOrString, serde_as};
9use solana_account_decoder_client_types::UiAccount;
10use solana_clock::{Clock, Epoch};
11use solana_epoch_info::EpochInfo;
12use solana_message::inner_instruction::InnerInstructionsList;
13use solana_pubkey::Pubkey;
14use solana_signature::Signature;
15use solana_transaction::versioned::VersionedTransaction;
16use solana_transaction_context::TransactionReturnData;
17use solana_transaction_error::TransactionError;
18use txtx_addon_network_svm_types::subgraph::SubgraphRequest;
19use uuid::Uuid;
20
21pub const DEFAULT_RPC_URL: &str = "https://api.mainnet-beta.solana.com";
22pub const DEFAULT_RPC_PORT: u16 = 8899;
23pub const DEFAULT_WS_PORT: u16 = 8900;
24pub const DEFAULT_STUDIO_PORT: u16 = 8488;
25pub const CHANGE_TO_DEFAULT_STUDIO_PORT_ONCE_SUPERVISOR_MERGED: u16 = 18488;
26pub const DEFAULT_NETWORK_HOST: &str = "127.0.0.1";
27
28#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
29pub struct TransactionMetadata {
30    pub signature: Signature,
31    pub logs: Vec<String>,
32    pub inner_instructions: InnerInstructionsList,
33    pub compute_units_consumed: u64,
34    pub return_data: TransactionReturnData,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub enum TransactionConfirmationStatus {
40    Processed,
41    Confirmed,
42    Finalized,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
46pub enum BlockProductionMode {
47    #[default]
48    Clock,
49    Transaction,
50    Manual,
51}
52
53#[derive(Debug)]
54pub enum SubgraphEvent {
55    EndpointReady,
56    InfoLog(DateTime<Local>, String),
57    ErrorLog(DateTime<Local>, String),
58    WarnLog(DateTime<Local>, String),
59    DebugLog(DateTime<Local>, String),
60    Shutdown,
61}
62
63impl SubgraphEvent {
64    pub fn info<S>(msg: S) -> Self
65    where
66        S: Into<String>,
67    {
68        Self::InfoLog(Local::now(), msg.into())
69    }
70
71    pub fn warn<S>(msg: S) -> Self
72    where
73        S: Into<String>,
74    {
75        Self::WarnLog(Local::now(), msg.into())
76    }
77
78    pub fn error<S>(msg: S) -> Self
79    where
80        S: Into<String>,
81    {
82        Self::ErrorLog(Local::now(), msg.into())
83    }
84
85    pub fn debug<S>(msg: S) -> Self
86    where
87        S: Into<String>,
88    {
89        Self::DebugLog(Local::now(), msg.into())
90    }
91}
92
93/// Result structure for compute units estimation.
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95#[serde(rename_all = "camelCase")]
96pub struct ComputeUnitsEstimationResult {
97    pub success: bool,
98    pub compute_units_consumed: u64,
99    pub log_messages: Option<Vec<String>>,
100    pub error_message: Option<String>,
101}
102
103/// The struct for storing the profiling results.
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
105#[serde(rename_all = "camelCase")]
106pub struct ProfileResult {
107    pub compute_units: ComputeUnitsEstimationResult,
108    pub state: ProfileState,
109    pub slot: u64,
110    pub uuid: Option<Uuid>,
111}
112
113impl ProfileResult {
114    pub fn success(
115        compute_units_consumed: u64,
116        logs: Vec<String>,
117        pre_execution: BTreeMap<Pubkey, Option<UiAccount>>,
118        post_execution: BTreeMap<Pubkey, Option<UiAccount>>,
119        slot: u64,
120        uuid: Option<Uuid>,
121    ) -> Self {
122        Self {
123            compute_units: ComputeUnitsEstimationResult {
124                success: true,
125                compute_units_consumed,
126                log_messages: Some(logs),
127                error_message: None,
128            },
129            state: ProfileState::new(pre_execution, post_execution),
130            slot,
131            uuid,
132        }
133    }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
137#[serde(rename_all = "camelCase")]
138
139pub struct ProfileState {
140    #[serde(with = "profile_state_map")]
141    pub pre_execution: BTreeMap<Pubkey, Option<UiAccount>>,
142    #[serde(with = "profile_state_map")]
143    pub post_execution: BTreeMap<Pubkey, Option<UiAccount>>,
144}
145
146impl ProfileState {
147    pub fn new(
148        pre_execution: BTreeMap<Pubkey, Option<UiAccount>>,
149        post_execution: BTreeMap<Pubkey, Option<UiAccount>>,
150    ) -> Self {
151        Self {
152            pre_execution,
153            post_execution,
154        }
155    }
156}
157
158pub mod profile_state_map {
159    use super::*;
160
161    pub fn serialize<S>(
162        map: &BTreeMap<Pubkey, Option<UiAccount>>,
163        serializer: S,
164    ) -> Result<S::Ok, S::Error>
165    where
166        S: Serializer,
167    {
168        let str_map: BTreeMap<String, &Option<UiAccount>> =
169            map.iter().map(|(k, v)| (k.to_string(), v)).collect();
170        str_map.serialize(serializer)
171    }
172
173    pub fn deserialize<'de, D>(
174        deserializer: D,
175    ) -> Result<BTreeMap<Pubkey, Option<UiAccount>>, D::Error>
176    where
177        D: Deserializer<'de>,
178    {
179        let str_map: BTreeMap<String, Option<UiAccount>> = BTreeMap::deserialize(deserializer)?;
180        str_map
181            .into_iter()
182            .map(|(k, v)| {
183                Pubkey::from_str(&k)
184                    .map(|pk| (pk, v))
185                    .map_err(serde::de::Error::custom)
186            })
187            .collect()
188    }
189}
190
191#[derive(Debug, Clone)]
192pub enum SubgraphCommand {
193    CreateCollection(Uuid, SubgraphRequest, Sender<String>),
194    ObserveCollection(Receiver<DataIndexingCommand>),
195    Shutdown,
196}
197
198#[derive(Debug)]
199pub enum SimnetEvent {
200    Ready,
201    Connected(String),
202    Aborted(String),
203    Shutdown,
204    ClockUpdate(Clock),
205    EpochInfoUpdate(EpochInfo),
206    BlockHashExpired,
207    InfoLog(DateTime<Local>, String),
208    ErrorLog(DateTime<Local>, String),
209    WarnLog(DateTime<Local>, String),
210    DebugLog(DateTime<Local>, String),
211    PluginLoaded(String),
212    TransactionReceived(DateTime<Local>, VersionedTransaction),
213    TransactionProcessed(
214        DateTime<Local>,
215        TransactionMetadata,
216        Option<TransactionError>,
217    ),
218    AccountUpdate(DateTime<Local>, Pubkey),
219    TaggedProfile {
220        result: ProfileResult,
221        tag: String,
222        timestamp: DateTime<Local>,
223    },
224    RunbookStarted(String),
225    RunbookCompleted(String),
226}
227
228impl SimnetEvent {
229    pub fn info<S>(msg: S) -> Self
230    where
231        S: Into<String>,
232    {
233        Self::InfoLog(Local::now(), msg.into())
234    }
235
236    pub fn warn<S>(msg: S) -> Self
237    where
238        S: Into<String>,
239    {
240        Self::WarnLog(Local::now(), msg.into())
241    }
242
243    pub fn error<S>(msg: S) -> Self
244    where
245        S: Into<String>,
246    {
247        Self::ErrorLog(Local::now(), msg.into())
248    }
249
250    pub fn debug<S>(msg: S) -> Self
251    where
252        S: Into<String>,
253    {
254        Self::DebugLog(Local::now(), msg.into())
255    }
256
257    pub fn transaction_processed(meta: TransactionMetadata, err: Option<TransactionError>) -> Self {
258        Self::TransactionProcessed(Local::now(), meta, err)
259    }
260
261    pub fn transaction_received(tx: VersionedTransaction) -> Self {
262        Self::TransactionReceived(Local::now(), tx)
263    }
264
265    pub fn account_update(pubkey: Pubkey) -> Self {
266        Self::AccountUpdate(Local::now(), pubkey)
267    }
268
269    pub fn tagged_profile(result: ProfileResult, tag: String) -> Self {
270        Self::TaggedProfile {
271            result,
272            tag,
273            timestamp: Local::now(),
274        }
275    }
276
277    pub fn account_update_msg(&self) -> String {
278        match self {
279            SimnetEvent::AccountUpdate(_, pubkey) => {
280                format!("Account {} updated.", pubkey)
281            }
282            _ => unreachable!("This function should only be called for AccountUpdate events"),
283        }
284    }
285
286    pub fn epoch_info_update_msg(&self) -> String {
287        match self {
288            SimnetEvent::EpochInfoUpdate(epoch_info) => {
289                format!(
290                    "Datasource connection successful. Epoch {} / Slot index {} / Slot {}.",
291                    epoch_info.epoch, epoch_info.slot_index, epoch_info.absolute_slot
292                )
293            }
294            _ => unreachable!("This function should only be called for EpochInfoUpdate events"),
295        }
296    }
297
298    pub fn plugin_loaded_msg(&self) -> String {
299        match self {
300            SimnetEvent::PluginLoaded(plugin_name) => {
301                format!("Plugin {} successfully loaded.", plugin_name)
302            }
303            _ => unreachable!("This function should only be called for PluginLoaded events"),
304        }
305    }
306
307    pub fn clock_update_msg(&self) -> String {
308        match self {
309            SimnetEvent::ClockUpdate(clock) => {
310                format!("Clock ticking (epoch {}, slot {})", clock.epoch, clock.slot)
311            }
312            _ => unreachable!("This function should only be called for ClockUpdate events"),
313        }
314    }
315}
316
317#[derive(Debug)]
318pub enum TransactionStatusEvent {
319    Success(TransactionConfirmationStatus),
320    SimulationFailure((TransactionError, TransactionMetadata)),
321    ExecutionFailure((TransactionError, TransactionMetadata)),
322    VerificationFailure(String),
323}
324
325#[derive(Debug)]
326pub enum SimnetCommand {
327    SlotForward(Option<Hash>),
328    SlotBackward(Option<Hash>),
329    UpdateClock(ClockCommand),
330    UpdateBlockProductionMode(BlockProductionMode),
331    TransactionReceived(
332        Option<Hash>,
333        VersionedTransaction,
334        Sender<TransactionStatusEvent>,
335        bool,
336    ),
337    Terminate(Option<Hash>),
338}
339
340#[derive(Debug)]
341pub enum ClockCommand {
342    Pause,
343    Resume,
344    Toggle,
345    UpdateSlotInterval(u64),
346}
347
348pub enum ClockEvent {
349    Tick,
350    ExpireBlockHash,
351}
352
353#[derive(Clone, Debug, Default, Serialize)]
354pub struct SanitizedConfig {
355    pub rpc_url: String,
356    pub ws_url: String,
357    pub rpc_datasource_url: String,
358    pub studio_url: String,
359    pub graphql_query_route_url: String,
360}
361
362#[derive(Clone, Debug, Default)]
363pub struct SurfpoolConfig {
364    pub simnets: Vec<SimnetConfig>,
365    pub rpc: RpcConfig,
366    pub subgraph: SubgraphConfig,
367    pub studio: StudioConfig,
368    pub plugin_config_path: Vec<PathBuf>,
369}
370
371#[derive(Clone, Debug)]
372pub struct SimnetConfig {
373    pub remote_rpc_url: String,
374    pub slot_time: u64,
375    pub block_production_mode: BlockProductionMode,
376    pub airdrop_addresses: Vec<Pubkey>,
377    pub airdrop_token_amount: u64,
378    pub expiry: Option<u64>,
379}
380
381impl Default for SimnetConfig {
382    fn default() -> Self {
383        Self {
384            remote_rpc_url: DEFAULT_RPC_URL.to_string(),
385            slot_time: 0,
386            block_production_mode: BlockProductionMode::Clock,
387            airdrop_addresses: vec![],
388            airdrop_token_amount: 0,
389            expiry: None,
390        }
391    }
392}
393
394impl SimnetConfig {
395    pub fn get_sanitized_datasource_url(&self) -> String {
396        self.remote_rpc_url
397            .split("?")
398            .map(|e| e.to_string())
399            .collect::<Vec<String>>()
400            .first()
401            .expect("datasource url invalid")
402            .to_string()
403    }
404}
405
406#[derive(Clone, Debug, Default)]
407pub struct SubgraphConfig {}
408
409#[derive(Clone, Debug)]
410pub struct RpcConfig {
411    pub bind_host: String,
412    pub bind_port: u16,
413    pub ws_port: u16,
414}
415
416impl RpcConfig {
417    pub fn get_rpc_base_url(&self) -> String {
418        format!("{}:{}", self.bind_host, self.bind_port)
419    }
420    pub fn get_ws_base_url(&self) -> String {
421        format!("{}:{}", self.bind_host, self.ws_port)
422    }
423}
424
425impl Default for RpcConfig {
426    fn default() -> Self {
427        Self {
428            bind_host: DEFAULT_NETWORK_HOST.to_string(),
429            bind_port: DEFAULT_RPC_PORT,
430            ws_port: DEFAULT_WS_PORT,
431        }
432    }
433}
434
435#[derive(Clone, Debug)]
436pub struct StudioConfig {
437    pub bind_host: String,
438    pub bind_port: u16,
439}
440
441impl StudioConfig {
442    pub fn get_studio_base_url(&self) -> String {
443        format!("{}:{}", self.bind_host, self.bind_port)
444    }
445}
446
447impl Default for StudioConfig {
448    fn default() -> Self {
449        Self {
450            bind_host: DEFAULT_NETWORK_HOST.to_string(),
451            bind_port: CHANGE_TO_DEFAULT_STUDIO_PORT_ONCE_SUPERVISOR_MERGED,
452        }
453    }
454}
455
456#[derive(Debug, Clone, Deserialize, Serialize)]
457pub struct SubgraphPluginConfig {
458    pub uuid: Uuid,
459    pub ipc_token: String,
460    pub subgraph_request: SubgraphRequest,
461}
462
463#[derive(Serialize, Deserialize, Clone, Debug)]
464pub struct SvmSimnetInitializationRequest {
465    pub domain: String,
466    pub block_production_mode: BlockProductionMode,
467    pub datasource_rpc_url: String,
468}
469
470#[derive(Serialize, Deserialize, Clone, Debug)]
471pub enum SvmSimnetCommand {
472    Init(SvmSimnetInitializationRequest),
473}
474
475#[derive(Serialize, Deserialize)]
476pub struct CreateNetworkRequest {
477    pub workspace_id: Uuid,
478    pub name: String,
479    pub description: Option<String>,
480    pub datasource_rpc_url: String,
481    pub block_production_mode: BlockProductionMode,
482}
483
484impl CreateNetworkRequest {
485    pub fn new(
486        workspace_id: Uuid,
487        name: String,
488        description: Option<String>,
489        datasource_rpc_url: String,
490        block_production_mode: BlockProductionMode,
491    ) -> Self {
492        Self {
493            workspace_id,
494            name,
495            description,
496            datasource_rpc_url,
497            block_production_mode,
498        }
499    }
500}
501
502#[derive(Serialize, Deserialize)]
503pub struct CreateNetworkResponse {
504    pub rpc_url: String,
505}
506
507#[derive(Serialize, Deserialize)]
508pub struct DeleteNetworkRequest {
509    pub workspace_id: Uuid,
510    pub network_id: Uuid,
511}
512
513impl DeleteNetworkRequest {
514    pub fn new(workspace_id: Uuid, network_id: Uuid) -> Self {
515        Self {
516            workspace_id,
517            network_id,
518        }
519    }
520}
521
522#[derive(Serialize, Deserialize)]
523pub struct DeleteNetworkResponse;
524
525#[serde_as]
526#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
527#[serde(rename_all = "camelCase")]
528pub struct AccountUpdate {
529    /// providing this value sets the lamports in the account
530    pub lamports: Option<u64>,
531    /// providing this value sets the data held in this account
532    #[serde_as(as = "Option<BytesOrString>")]
533    pub data: Option<Vec<u8>>,
534    ///  providing this value sets the program that owns this account. If executable, the program that loads this account.
535    pub owner: Option<String>,
536    /// providing this value sets whether this account's data contains a loaded program (and is now read-only)
537    pub executable: Option<bool>,
538    /// providing this value sets the epoch at which this account will next owe rent
539    pub rent_epoch: Option<Epoch>,
540}
541
542#[derive(Debug, Clone)]
543pub enum SetSomeAccount {
544    Account(String),
545    NoAccount,
546}
547
548impl<'de> Deserialize<'de> for SetSomeAccount {
549    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
550    where
551        D: Deserializer<'de>,
552    {
553        struct SetSomeAccountVisitor;
554
555        impl<'de> Visitor<'de> for SetSomeAccountVisitor {
556            type Value = SetSomeAccount;
557
558            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
559                formatter.write_str("a Pubkey String or the String 'null'")
560            }
561
562            fn visit_some<D_>(self, deserializer: D_) -> std::result::Result<Self::Value, D_::Error>
563            where
564                D_: Deserializer<'de>,
565            {
566                Deserialize::deserialize(deserializer).map(|v: String| match v.as_str() {
567                    "null" => SetSomeAccount::NoAccount,
568                    _ => SetSomeAccount::Account(v.to_string()),
569                })
570            }
571        }
572
573        deserializer.deserialize_option(SetSomeAccountVisitor)
574    }
575}
576
577impl Serialize for SetSomeAccount {
578    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
579    where
580        S: Serializer,
581    {
582        match self {
583            SetSomeAccount::Account(val) => serializer.serialize_str(val),
584            SetSomeAccount::NoAccount => serializer.serialize_str("null"),
585        }
586    }
587}
588
589#[serde_as]
590#[derive(Debug, Clone, Default, Serialize, Deserialize)]
591#[serde(rename_all = "camelCase")]
592pub struct TokenAccountUpdate {
593    /// providing this value sets the amount of the token in the account data
594    pub amount: Option<u64>,
595    /// providing this value sets the delegate of the token account
596    pub delegate: Option<SetSomeAccount>,
597    /// providing this value sets the state of the token account
598    pub state: Option<String>,
599    /// providing this value sets the amount authorized to the delegate
600    pub delegated_amount: Option<u64>,
601    /// providing this value sets the close authority of the token account
602    pub close_authority: Option<SetSomeAccount>,
603}
604
605// token supply update for set supply method in SVM tricks
606#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
607pub struct SupplyUpdate {
608    pub total: Option<u64>,
609    pub circulating: Option<u64>,
610    pub non_circulating: Option<u64>,
611    pub non_circulating_accounts: Option<Vec<String>>,
612}
613
614#[derive(Clone, Debug, Serialize)]
615pub enum UuidOrSignature {
616    Uuid(Uuid),
617    Signature(Signature),
618}
619
620impl<'de> Deserialize<'de> for UuidOrSignature {
621    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
622    where
623        D: Deserializer<'de>,
624    {
625        let s = String::deserialize(deserializer)?;
626
627        if let Ok(uuid) = Uuid::parse_str(&s) {
628            return Ok(UuidOrSignature::Uuid(uuid));
629        }
630
631        if let Ok(signature) = s.parse::<Signature>() {
632            return Ok(UuidOrSignature::Signature(signature));
633        }
634
635        Err(serde::de::Error::custom(
636            "expected a Uuid or a valid Solana Signature",
637        ))
638    }
639}
640
641#[derive(Debug, Clone, Deserialize, Serialize)]
642pub enum DataIndexingCommand {
643    ProcessCollection(Uuid),
644    ProcessCollectionEntriesPack(Uuid, Vec<u8>),
645}