switchboard_solana/attestation_program/client/
routine.rs1use 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 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 if let Some(attestation_queue) = &self.attestation_queue {
27 filters.push(FunctionRoutineAccountData::get_queue_filter(
28 attestation_queue,
29 ));
30 }
31
32 if let Some(authority) = &self.authority {
34 filters.push(FunctionRoutineAccountData::get_authority_filter(authority));
35 }
36
37 if let Some(metadata) = &self.metadata {
39 filters.push(FunctionRoutineAccountData::get_metadata_filter(
40 metadata.clone(),
41 ));
42 }
43
44 if let Some(is_enabled) = &self.is_enabled {
46 if *is_enabled {
47 filters.push(FunctionRoutineAccountData::get_is_enabled_filter());
48 }
49 }
50
51 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 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 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 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 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}