switchboard_solana/attestation_program/client/
routine.rs

1use crate::*;
2
3use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig};
4use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
5
6
7use std::sync::Arc;
8
9#[derive(Default, Debug, Clone)]
10pub struct FunctionRoutineFilters {
11    pub attestation_queue: Option<Pubkey>,
12    pub authority: Option<Pubkey>,
13    pub metadata: Option<Vec<u8>>,
14    pub is_enabled: Option<bool>,
15    // Note: This will not catch functions for which the verifier fetching
16    // is the secondary oracle. This is because the verifier is not stored
17    // on the function account but on the queue account.
18    pub queue_idx: Option<u32>,
19}
20
21impl FunctionRoutineFilters {
22    pub fn to_vec(&self) -> Vec<solana_client::rpc_filter::RpcFilterType> {
23        let mut filters = vec![FunctionRoutineAccountData::get_discriminator_filter()];
24
25        // AttestationQueue Filter
26        if let Some(attestation_queue) = &self.attestation_queue {
27            filters.push(FunctionRoutineAccountData::get_queue_filter(
28                attestation_queue,
29            ));
30        }
31
32        // Authority Filter
33        if let Some(authority) = &self.authority {
34            filters.push(FunctionRoutineAccountData::get_authority_filter(authority));
35        }
36
37        // Metadata Filter
38        if let Some(metadata) = &self.metadata {
39            filters.push(FunctionRoutineAccountData::get_metadata_filter(
40                metadata.clone(),
41            ));
42        }
43
44        // Is Enabled Filter
45        if let Some(is_enabled) = &self.is_enabled {
46            if *is_enabled {
47                filters.push(FunctionRoutineAccountData::get_is_enabled_filter());
48            }
49        }
50
51        // Queue Idx Filter
52        if let Some(queue_idx) = &self.queue_idx {
53            filters.push(FunctionRoutineAccountData::get_queue_idx_filter(queue_idx));
54        }
55
56        filters
57    }
58}
59
60impl FunctionRoutineAccountData {
61    /////////////////////////////////////////////////////////////
62    /// Client Methods
63    /////////////////////////////////////////////////////////////
64
65    pub async fn get_program_accounts(
66        rpc: &solana_client::nonblocking::rpc_client::RpcClient,
67        filters: FunctionRoutineFilters,
68        commitment: Option<CommitmentLevel>,
69    ) -> Result<Vec<(Pubkey, FunctionRoutineAccountData)>, SbError> {
70        let mut routines = vec![];
71
72        let accounts = rpc
73            .get_program_accounts_with_config(
74                &SWITCHBOARD_ATTESTATION_PROGRAM_ID,
75                RpcProgramAccountsConfig {
76                    filters: Some(filters.to_vec()),
77                    account_config: RpcAccountInfoConfig {
78                        encoding: Some(solana_account_decoder::UiAccountEncoding::Base64Zstd),
79                        commitment: Some(CommitmentConfig {
80                            commitment: commitment.unwrap_or(CommitmentLevel::Processed),
81                        }),
82                        ..Default::default()
83                    },
84
85                    ..Default::default()
86                },
87            )
88            .await
89            .map_err(|e| SbError::CustomError {
90                message: "Failed to get program accounts".to_string(),
91                source: Arc::new(e),
92            })?;
93
94        for (pubkey, account) in accounts {
95            if let Ok(routine_data) =
96                FunctionRoutineAccountData::try_deserialize(&mut &account.data[..])
97            {
98                routines.push((pubkey, routine_data));
99            }
100        }
101
102        Ok(routines)
103    }
104
105    /////////////////////////////////////////////////////////////
106    /// Fetch Methods
107    /////////////////////////////////////////////////////////////
108
109    pub fn fetch(
110        client: &solana_client::rpc_client::RpcClient,
111        pubkey: Pubkey,
112    ) -> std::result::Result<Self, switchboard_common::SbError> {
113        crate::client::fetch_borsh_account(client, pubkey)
114    }
115
116    pub async fn fetch_async(
117        client: &solana_client::nonblocking::rpc_client::RpcClient,
118        pubkey: Pubkey,
119    ) -> std::result::Result<Self, switchboard_common::SbError> {
120        crate::client::fetch_borsh_account_async(client, pubkey).await
121    }
122
123    pub fn fetch_sync<T: solana_sdk::client::SyncClient>(
124        client: &T,
125        pubkey: Pubkey,
126    ) -> std::result::Result<Self, switchboard_common::SbError> {
127        crate::client::fetch_borsh_account_sync(client, pubkey)
128    }
129
130    //
131
132    pub fn get_discriminator_filter() -> solana_client::rpc_filter::RpcFilterType {
133        solana_client::rpc_filter::RpcFilterType::Memcmp(
134            solana_client::rpc_filter::Memcmp::new_raw_bytes(
135                0,
136                FunctionRoutineAccountData::discriminator().to_vec(),
137            ),
138        )
139    }
140
141    pub fn get_authority_filter(
142        authority_pubkey: &Pubkey,
143    ) -> solana_client::rpc_filter::RpcFilterType {
144        solana_client::rpc_filter::RpcFilterType::Memcmp(
145            solana_client::rpc_filter::Memcmp::new_raw_bytes(
146                419,
147                authority_pubkey.to_bytes().into(),
148            ),
149        )
150    }
151
152    pub fn get_queue_filter(queue_pubkey: &Pubkey) -> solana_client::rpc_filter::RpcFilterType {
153        solana_client::rpc_filter::RpcFilterType::Memcmp(
154            solana_client::rpc_filter::Memcmp::new_raw_bytes(515, queue_pubkey.to_bytes().into()),
155        )
156    }
157
158    pub fn get_queue_idx_filter(queue_idx: &u32) -> solana_client::rpc_filter::RpcFilterType {
159        solana_client::rpc_filter::RpcFilterType::Memcmp(
160            solana_client::rpc_filter::Memcmp::new_raw_bytes(611, queue_idx.to_le_bytes().to_vec()),
161        )
162    }
163
164    pub fn get_is_enabled_filter() -> solana_client::rpc_filter::RpcFilterType {
165        solana_client::rpc_filter::RpcFilterType::Memcmp(
166            solana_client::rpc_filter::Memcmp::new_raw_bytes(344, 0u8.to_le_bytes().to_vec()),
167        )
168    }
169
170    pub fn get_metadata_filter(metadata: Vec<u8>) -> solana_client::rpc_filter::RpcFilterType {
171        solana_client::rpc_filter::RpcFilterType::Memcmp(
172            solana_client::rpc_filter::Memcmp::new_raw_bytes(72, metadata),
173        )
174    }
175
176    pub fn get_schedule(&self) -> Option<cron::Schedule> {
177        if self.schedule[0] == 0 {
178            return None;
179        }
180        let every_second = cron::Schedule::try_from("* * * * * *").unwrap();
181        let schedule = std::str::from_utf8(&self.schedule)
182            .unwrap_or("* * * * * *")
183            .trim_end_matches('\0');
184        let schedule = cron::Schedule::try_from(schedule);
185        Some(schedule.unwrap_or(every_second))
186    }
187
188    pub fn get_last_execution_datetime(&self) -> chrono::DateTime<chrono::Utc> {
189        chrono::NaiveDateTime::from_timestamp_opt(self.last_execution_timestamp, 0)
190            .unwrap()
191            .and_utc()
192    }
193
194    pub fn get_next_execution_datetime(&self) -> Option<chrono::DateTime<chrono::Utc>> {
195        let schedule = self.get_schedule()?;
196
197        // If we havent ever executed, use the current timestamp
198        let last_execution_timestamp = if self.last_execution_timestamp > 0 {
199            self.last_execution_timestamp
200        } else {
201            unix_timestamp()
202        };
203        let last_execution_datetime =
204            chrono::NaiveDateTime::from_timestamp_opt(last_execution_timestamp, 0)
205                .unwrap()
206                .and_utc();
207
208        schedule.after(&last_execution_datetime).next()
209    }
210
211    pub fn should_execute(&self, now: chrono::DateTime<chrono::Utc>) -> bool {
212        let schedule = self.get_schedule();
213        if schedule.is_none() {
214            return false;
215        }
216        if self.last_execution_timestamp == 0 {
217            return true;
218        }
219        let dt = self.get_last_execution_datetime();
220        let next_trigger_time = schedule.unwrap().after(&dt).next();
221        if next_trigger_time.is_none() {
222            return false;
223        }
224        let next_trigger_time = next_trigger_time.unwrap();
225        if next_trigger_time > now {
226            return false;
227        }
228        true
229    }
230
231    pub fn calc_container_params_hash(container_params: &Vec<u8>) -> [u8; 32] {
232        solana_program::hash::hash(container_params).to_bytes()
233    }
234
235    pub fn get_container_params_hash(&self) -> [u8; 32] {
236        solana_program::hash::hash(&self.container_params).to_bytes()
237    }
238}