switchboard_solana/attestation_program/client/
request.rs

1use crate::*;
2
3use kv_log_macro::info;
4use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig};
5use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
6use solana_sdk::signer::Signer;
7use std::sync::Arc;
8
9#[derive(Default, Debug, Clone)]
10pub struct FunctionRequestFilters {
11    pub attestation_queue: Option<Pubkey>,
12    pub authority: Option<Pubkey>,
13    pub is_triggered: Option<bool>,
14    pub is_active: 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 FunctionRequestFilters {
22    pub fn to_vec(&self) -> Vec<solana_client::rpc_filter::RpcFilterType> {
23        let mut filters = vec![FunctionRequestAccountData::get_discriminator_filter()];
24
25        // AttestationQueue Filter
26        if let Some(attestation_queue) = &self.attestation_queue {
27            filters.push(FunctionRequestAccountData::get_queue_filter(
28                attestation_queue,
29            ));
30        }
31
32        // Authority Filter
33        if let Some(authority) = &self.authority {
34            filters.push(FunctionRequestAccountData::get_authority_filter(authority));
35        }
36
37        // Combine filters for efficiency
38        if self.is_triggered.is_some() && self.is_active.is_some() {
39            filters.push(FunctionRequestAccountData::get_is_triggered_and_active_filter());
40        } else {
41            // Is Triggered Filter
42            if let Some(is_triggered) = &self.is_triggered {
43                if *is_triggered {
44                    filters.push(FunctionRequestAccountData::get_is_triggered_filter());
45                }
46            }
47
48            // Is Active Filter
49            if let Some(is_active) = &self.is_active {
50                if *is_active {
51                    filters.push(FunctionRequestAccountData::get_is_active_filter());
52                }
53            }
54        }
55
56        // Queue Idx Filter
57        if let Some(queue_idx) = &self.queue_idx {
58            filters.push(FunctionRequestAccountData::get_queue_idx_filter(queue_idx));
59        }
60
61        filters
62    }
63}
64
65impl FunctionRequestAccountData {
66    /////////////////////////////////////////////////////////////
67    /// Client Methods
68    /////////////////////////////////////////////////////////////
69    ///
70
71    pub async fn get_program_accounts(
72        rpc: &solana_client::nonblocking::rpc_client::RpcClient,
73        filters: FunctionRequestFilters,
74        commitment: Option<CommitmentLevel>,
75    ) -> Result<Vec<(Pubkey, FunctionRequestAccountData)>, SbError> {
76        let mut requests = vec![];
77
78        let accounts = rpc
79            .get_program_accounts_with_config(
80                &SWITCHBOARD_ATTESTATION_PROGRAM_ID,
81                RpcProgramAccountsConfig {
82                    filters: Some(filters.to_vec()),
83                    account_config: RpcAccountInfoConfig {
84                        encoding: Some(solana_account_decoder::UiAccountEncoding::Base64Zstd),
85                        commitment: Some(CommitmentConfig {
86                            commitment: commitment.unwrap_or(CommitmentLevel::Processed),
87                        }),
88                        ..Default::default()
89                    },
90                    ..Default::default()
91                },
92            )
93            .await
94            .map_err(|e| SbError::CustomError {
95                message: "Failed to get program accounts".to_string(),
96                source: Arc::new(e),
97            })?;
98
99        for (pubkey, account) in accounts {
100            if let Ok(request_data) =
101                FunctionRequestAccountData::try_deserialize(&mut &account.data[..])
102            {
103                requests.push((pubkey, request_data));
104            }
105        }
106
107        Ok(requests)
108    }
109
110    pub async fn get_or_create_from_seed(
111        rpc: &solana_client::nonblocking::rpc_client::RpcClient,
112        payer: std::sync::Arc<Keypair>,
113        function: Pubkey,
114        seed: Option<&str>,
115        params: Option<FunctionRequestInitAndTriggerParams>,
116    ) -> Result<Pubkey, SbError> {
117        let params = params.unwrap_or_default();
118        let request_keypair = crate::keypair_from_base_seed(
119            format!("request-{}-{}", function, seed.unwrap_or("default")).as_str(),
120            payer.secret().to_bytes().to_vec(),
121            Some(params.try_to_vec().unwrap()),
122        )
123        .unwrap();
124        let request_pubkey = request_keypair.pubkey();
125
126        if let Err(SbError::AccountNotFound) =
127            FunctionRequestAccountData::fetch_async(rpc, request_pubkey).await
128        {
129            info!(
130                "[Request] creating new request account {} ...",
131                request_pubkey
132            );
133
134            // Fetch function_data and determine if we need authority signer
135            let function_data = FunctionAccountData::fetch_async(rpc, function)
136                .await
137                .unwrap();
138            if function_data.requests_require_authorization != 0
139                && payer.pubkey() != function_data.authority
140            {
141                return Err(SbError::Message("MissingAuthoritySigner"));
142            }
143
144            // Build the request account and trigger it
145            let req_init_ixn = FunctionRequestInitAndTrigger::build_ix(
146                &FunctionRequestInitAndTriggerAccounts {
147                    request: request_pubkey,
148                    authority: payer.pubkey(),
149                    function,
150                    function_authority: None,
151                    attestation_queue: function_data.attestation_queue,
152                    payer: payer.pubkey(),
153                },
154                &params,
155            )
156            .unwrap();
157
158            let tx = crate::ix_to_tx(
159                &[req_init_ixn],
160                &[&*payer, &request_keypair],
161                rpc.get_latest_blockhash().await.unwrap_or_default(),
162            )
163            .unwrap();
164
165            let signature = rpc.send_and_confirm_transaction(&tx).await.unwrap();
166
167            info!(
168                "[Request] request {} initialized. Tx Signature: {}",
169                request_pubkey, signature
170            );
171        };
172
173        Ok(request_pubkey)
174    }
175
176    /////////////////////////////////////////////////////////////
177    /// Fetch Methods
178    /////////////////////////////////////////////////////////////
179
180    pub fn fetch(
181        client: &solana_client::rpc_client::RpcClient,
182        pubkey: Pubkey,
183    ) -> std::result::Result<Self, switchboard_common::SbError> {
184        crate::client::fetch_borsh_account(client, pubkey)
185    }
186
187    pub async fn fetch_async(
188        client: &solana_client::nonblocking::rpc_client::RpcClient,
189        pubkey: Pubkey,
190    ) -> std::result::Result<Self, switchboard_common::SbError> {
191        crate::client::fetch_borsh_account_async(client, pubkey).await
192    }
193
194    pub fn fetch_sync<T: solana_sdk::client::SyncClient>(
195        client: &T,
196        pubkey: Pubkey,
197    ) -> std::result::Result<Self, switchboard_common::SbError> {
198        crate::client::fetch_borsh_account_sync(client, pubkey)
199    }
200
201    //
202
203    pub fn get_discriminator_filter() -> solana_client::rpc_filter::RpcFilterType {
204        solana_client::rpc_filter::RpcFilterType::Memcmp(
205            solana_client::rpc_filter::Memcmp::new_raw_bytes(
206                0,
207                FunctionRequestAccountData::discriminator().to_vec(),
208            ),
209        )
210    }
211
212    pub fn get_is_triggered_filter() -> solana_client::rpc_filter::RpcFilterType {
213        solana_client::rpc_filter::RpcFilterType::Memcmp(
214            solana_client::rpc_filter::Memcmp::new_raw_bytes(8, vec![1u8]),
215        )
216    }
217
218    pub fn get_is_active_filter() -> solana_client::rpc_filter::RpcFilterType {
219        solana_client::rpc_filter::RpcFilterType::Memcmp(
220            solana_client::rpc_filter::Memcmp::new_raw_bytes(
221                9,
222                vec![RequestStatus::RequestPending as u8],
223            ),
224        )
225    }
226
227    pub fn get_is_triggered_and_active_filter() -> solana_client::rpc_filter::RpcFilterType {
228        solana_client::rpc_filter::RpcFilterType::Memcmp(
229            solana_client::rpc_filter::Memcmp::new_raw_bytes(
230                8,
231                vec![1u8, RequestStatus::RequestPending as u8],
232            ),
233        )
234    }
235
236    pub fn get_queue_filter(queue_pubkey: &Pubkey) -> solana_client::rpc_filter::RpcFilterType {
237        solana_client::rpc_filter::RpcFilterType::Memcmp(
238            solana_client::rpc_filter::Memcmp::new_raw_bytes(138, queue_pubkey.to_bytes().into()),
239        )
240    }
241
242    pub fn get_queue_idx_filter(queue_idx: &u32) -> solana_client::rpc_filter::RpcFilterType {
243        solana_client::rpc_filter::RpcFilterType::Memcmp(
244            solana_client::rpc_filter::Memcmp::new_raw_bytes(275, queue_idx.to_le_bytes().to_vec()),
245        )
246    }
247
248    pub fn get_authority_filter(
249        authority_pubkey: &Pubkey,
250    ) -> solana_client::rpc_filter::RpcFilterType {
251        solana_client::rpc_filter::RpcFilterType::Memcmp(
252            solana_client::rpc_filter::Memcmp::new_raw_bytes(
253                10,
254                authority_pubkey.to_bytes().into(),
255            ),
256        )
257    }
258
259    pub fn get_is_ready_filters(
260        queue_pubkey: &Pubkey,
261    ) -> Vec<solana_client::rpc_filter::RpcFilterType> {
262        vec![
263            FunctionRequestAccountData::get_discriminator_filter(),
264            FunctionRequestAccountData::get_is_triggered_filter(),
265            FunctionRequestAccountData::get_is_active_filter(),
266            FunctionRequestAccountData::get_queue_filter(queue_pubkey),
267        ]
268    }
269
270    pub fn calc_container_params_hash(&self) -> [u8; 32] {
271        solana_program::hash::hash(&self.container_params).to_bytes()
272    }
273}