switchboard_solana/client/
validator.rs

1use crate::*;
2
3use anchor_client::solana_sdk::commitment_config::CommitmentConfig;
4use anchor_client::solana_sdk::transaction::{Transaction, TransactionError};
5use anchor_lang::Discriminator;
6use dashmap::DashMap;
7use sha2::{Digest, Sha256};
8use solana_client::nonblocking::rpc_client::RpcClient;
9use solana_client::rpc_config::RpcSimulateTransactionConfig;
10use solana_sdk::signature::Signature;
11use solana_sdk::signer::keypair::Keypair;
12use solana_sdk::signer::Signer;
13use std::sync::Arc;
14use tokio::sync::RwLock;
15use std::str::FromStr;
16
17pub type AnchorClient = anchor_client::Client<Arc<Keypair>>;
18pub type AnchorProgram = anchor_client::Program<Arc<Keypair>>;
19
20pub type QuoteVerifyFn = dyn (Fn(&[u8], i64) -> bool) + Send + Sync;
21
22#[derive(Default, Clone)]
23pub struct CacheEntry {
24    pub pubkey: Pubkey,
25    pub timestamp: i64,
26}
27
28#[derive(Clone, serde::Serialize, serde::Deserialize)]
29pub enum QvnReceipt {
30    /// QVN completed successfully with a exit status of 0
31    Success(String), // signature
32    /// QVN completed successfully with an error code [200-255]
33    SwitchboardError(String, u8), // signature, error code
34    /// QVN failed to verify user transaction and fellback to a default transaction
35    Fallback(String, u8), // signature, error code
36}
37
38#[derive(Clone)]
39pub struct FunctionResultValidatorCache {
40    pub timeout: Option<u32>,
41    pub function_escrow_wallet: Arc<DashMap<Pubkey, CacheEntry>>,
42    pub routine_escrow_wallet: Arc<DashMap<Pubkey, CacheEntry>>,
43}
44impl Default for FunctionResultValidatorCache {
45    fn default() -> Self {
46        Self {
47            timeout: Some(300),
48            function_escrow_wallet: Arc::new(DashMap::with_capacity(10_000)),
49            routine_escrow_wallet: Arc::new(DashMap::with_capacity(10_000)),
50        }
51    }
52}
53
54/// The list of accounts used by this verifier to verify the function result
55#[derive(Default, Debug, Clone)]
56pub struct FunctionResultValidatorAccounts {
57    pub payer: Pubkey,
58
59    pub verifier: Pubkey,
60    pub verifier_enclave_signer: Pubkey,
61    pub reward_receiver: Pubkey,
62
63    pub attestation_queue: Pubkey,
64    pub queue_authority: Pubkey,
65}
66
67/// The cleaned up parameters used for a verify instruction
68#[derive(Default, Debug, Clone)]
69pub struct FunctionValidatorVerifyParams {
70    pub mr_enclave: [u8; 32],
71    pub error_code: u8,
72    pub observed_time: i64,
73    pub container_params_hash: [u8; 32],
74    // optional
75    pub request_slot: u64,
76    pub next_allowed_timestamp: i64,
77}
78
79/// Represents a [`VerifierAccountData`] oracle and verifies an emitted FunctionResult
80#[derive(Clone)]
81pub struct FunctionResultValidator {
82    pub client: Arc<RwLock<AnchorClient>>,
83    pub rpc: Arc<RpcClient>,
84    pub payer: Arc<Keypair>,
85
86    // verifier fields
87    pub verifier: Arc<Pubkey>,
88    pub verifier_enclave_signer: FunctionResultValidatorSigner,
89    pub reward_receiver: Arc<Pubkey>,
90
91    // queue fields
92    pub attestation_queue: Arc<Pubkey>,
93    pub queue_authority: Arc<Pubkey>,
94
95    pub quote_verify_fn: Arc<Box<QuoteVerifyFn>>,
96
97    // cache some of the escrow pubkeys for faster execution
98    pub cache: FunctionResultValidatorCache,
99}
100
101pub struct FunctionResultValidatorInitAccounts {
102    pub verifier: Pubkey,
103    pub attestation_queue: Pubkey,
104    pub queue_authority: Pubkey,
105    pub reward_receiver: Pubkey,
106}
107
108#[derive(Debug, Clone)]
109pub enum FunctionResultValidatorSigner {
110    Simulation(Arc<Pubkey>),
111    Production(Arc<RwLock<Keypair>>),
112}
113
114impl FunctionResultValidator {
115    /// Create a new instance of the [`FunctionResultValidator`]
116    pub fn new(
117        client: Arc<RwLock<AnchorClient>>,
118        rpc: Arc<RpcClient>,
119        payer: Arc<Keypair>,
120        verifier_enclave_signer: FunctionResultValidatorSigner,
121        accounts: &FunctionResultValidatorInitAccounts,
122        quote_verify_fn: impl (Fn(&[u8], i64) -> bool) + 'static + Send + Sync,
123        cache: Option<FunctionResultValidatorCache>,
124    ) -> Self {
125        Self {
126            client: client.clone(),
127            rpc: rpc.clone(),
128            payer: payer.clone(),
129
130            verifier: Arc::new(accounts.verifier),
131            // verifier_enclave_keypair,
132            verifier_enclave_signer,
133            reward_receiver: Arc::new(accounts.reward_receiver),
134
135            attestation_queue: Arc::new(accounts.attestation_queue),
136            queue_authority: Arc::new(accounts.queue_authority),
137
138            quote_verify_fn: Arc::new(Box::new(quote_verify_fn)),
139
140            cache: cache.unwrap_or_default(),
141        }
142    }
143
144    pub async fn load(
145        client: Arc<RwLock<AnchorClient>>,
146        payer: Arc<Keypair>,
147        verifier: Pubkey,
148        verifier_enclave_signer: Option<FunctionResultValidatorSigner>,
149        reward_receiver: Option<Pubkey>,
150        quote_verify_fn: impl (Fn(&[u8], i64) -> bool) + 'static + Send + Sync,
151        cache: Option<FunctionResultValidatorCache>,
152    ) -> Result<Self, SbError> {
153        let rpc = get_async_rpc(&client).await?;
154
155        let verifier_data = VerifierAccountData::fetch_async(rpc.as_ref(), verifier).await?;
156
157        let verifier_enclave_signer = match verifier_enclave_signer {
158            Some(verifier_enclave_signer) => {
159                match &verifier_enclave_signer {
160                    FunctionResultValidatorSigner::Simulation(pubkey) => {
161                        if **pubkey != verifier_data.enclave.enclave_signer {
162                            return Err(
163                                SbError::Message(
164                                    "The provided verifier signer does not match the expected signer's pubkey"
165                                )
166                            );
167                        }
168                    }
169                    FunctionResultValidatorSigner::Production(keypair) => {
170                        let signer_pubkey = get_enclave_signer_pubkey(keypair).await?;
171                        if *signer_pubkey != verifier_data.enclave.enclave_signer {
172                            return Err(
173                                SbError::Message(
174                                    "The provided verifier signer does not match the expected signer's pubkey"
175                                )
176                            );
177                        }
178                    }
179                }
180                verifier_enclave_signer
181            }
182            None => FunctionResultValidatorSigner::Simulation(Arc::new(
183                verifier_data.enclave.enclave_signer,
184            )),
185        };
186
187        let attestation_queue =
188            AttestationQueueAccountData::fetch_async(&rpc, verifier_data.attestation_queue).await?;
189
190        Ok(FunctionResultValidator::new(
191            client,
192            rpc.clone(),
193            payer,
194            verifier_enclave_signer,
195            &(FunctionResultValidatorInitAccounts {
196                verifier,
197                attestation_queue: verifier_data.attestation_queue,
198                queue_authority: attestation_queue.authority,
199                reward_receiver: reward_receiver.unwrap_or_default(),
200            }),
201            quote_verify_fn,
202            cache,
203        ))
204    }
205
206    pub async fn load_from_cluster(
207        cluster: Cluster,
208        payer: Arc<Keypair>,
209        verifier: Pubkey,
210        verifier_enclave_signer: Option<FunctionResultValidatorSigner>,
211        reward_receiver: Option<Pubkey>,
212        quote_verify_fn: impl (Fn(&[u8], i64) -> bool) + 'static + Send + Sync,
213        cache: Option<FunctionResultValidatorCache>,
214    ) -> Result<Self, SbError> {
215        let client =
216            AnchorClient::new_with_options(cluster, payer.clone(), CommitmentConfig::processed());
217
218        Self::load(
219            Arc::new(RwLock::new(client)),
220            payer,
221            verifier,
222            verifier_enclave_signer,
223            reward_receiver,
224            quote_verify_fn,
225            cache,
226        )
227        .await
228    }
229
230    /// Whether the validator is in production mode and is ready to sign and send transactions.
231    pub fn is_production(&self) -> bool {
232        matches!(
233            &self.verifier_enclave_signer,
234            FunctionResultValidatorSigner::Production(_)
235        )
236    }
237
238    /// Whether the validator is in simulation mode and is ready to validate function requests.
239    pub fn is_simulation(&self) -> bool {
240        matches!(
241            &self.verifier_enclave_signer,
242            FunctionResultValidatorSigner::Simulation(_)
243        )
244    }
245
246    /// Check if the verifier_enclave_keypair is present so we can sign and send transactions.
247    async fn get_verifier_enclave_signer(&self) -> Result<Arc<Keypair>, SbError> {
248        match &self.verifier_enclave_signer {
249            FunctionResultValidatorSigner::Production(keypair) => {
250                // Kind of ugly but we re-create the keypair so we dont need to always await the lock
251                let kp = keypair.read().await;
252                let kp2 = Keypair::from_bytes(&kp.to_bytes()).unwrap();
253                Ok(Arc::new(kp2))
254            }
255            _ =>
256                Err(
257                    SbError::Message(
258                        "FunctionResultValidator is in simulation mode - please provide the verifier_enclave_keypair in order to process and send any transactions on behalf of the verifier oracle"
259                    )
260                ),
261        }
262    }
263
264    async fn get_verifier_enclave_pubkey(&self) -> Arc<Pubkey> {
265        match &self.verifier_enclave_signer {
266            FunctionResultValidatorSigner::Simulation(pubkey) => pubkey.clone(),
267            FunctionResultValidatorSigner::Production(keypair) => {
268                Arc::new(keypair.read().await.pubkey())
269            }
270        }
271    }
272
273    /// The entrypoint for the QVN. Verifies a FunctionResult and returns a transaction signature if successful.
274    pub async fn process(&self, function_result: &FunctionResult) -> Result<Signature, SbError> {
275        let (signature, _error_code) = match self.validate(function_result).await {
276            Ok(mut tx) => {
277                // Send transaction
278                // By this point it should have passed simulation and signature verification
279                (self.send_txn(&mut tx).await.unwrap(), None)
280            }
281            Err(err) => {
282                let function_pubkey =
283                    Pubkey::try_from_slice(function_result.fn_key().unwrap_or_default().as_slice())
284                        .unwrap_or_default();
285                // Try to catch error and send transaction
286
287                let error_code = match err {
288                    SbError::FunctionResultFailoverError(error_code, e) => {
289                        println!(
290                            "[QVN]({}) Failed to send transaction, sending fallback txn with error code ({}).\n{:?}",
291                            function_pubkey,
292                            error_code,
293                            e
294                        );
295                        Some(error_code)
296                    }
297                    SbError::FunctionResultNonRetryableError(e) => {
298                        println!(
299                            "[QVN]({}) Failed with non-retryable error.\n{:?}",
300                            function_pubkey, e
301                        );
302                        None
303                    }
304                    _ => {
305                        println!(
306                            "[QVN]({}) No error handler found for error {:?}",
307                            function_pubkey, err
308                        );
309                        Some(211) // improve this
310                    }
311                };
312
313                if let Some(error_code) = error_code {
314                    let mut tx = self
315                        .produce_failover_tx(function_result, Some(error_code))
316                        .await
317                        .unwrap();
318                    (self.send_txn(&mut tx).await.unwrap(), Some(error_code))
319                } else {
320                    (Signature::default(), None)
321                }
322            }
323        };
324
325        Ok(signature)
326    }
327
328    /// Validate the function result and return any errors
329    pub async fn validate(&self, function_result: &FunctionResult) -> Result<Transaction, SbError> {
330        let error_code = function_result.error_code();
331        // if error_code < 200 {
332        //     println!("[QVN] Function Result\n{:?}", function_result);
333        // }
334
335        // 1. Validate the [`FunctionResult`] is for the Solana chain
336        let solana_function_result =
337            if let Ok(switchboard_common::ChainResultInfo::Solana(chain_result_info)) =
338                function_result.chain_result_info()
339            {
340                chain_result_info
341            } else {
342                return Err(SbError::InvalidChain);
343            };
344
345        let function_pubkey =
346            Pubkey::try_from_slice(function_result.fn_key().unwrap().as_slice()).unwrap();
347        let function_enclave_signer = Pubkey::try_from_slice(function_result.signer()).unwrap();
348
349        // If the error_code is 200 or greater, we can skip the quote verification and just rebuild the tx ourselves
350        if error_code >= 200 {
351            return Ok(self
352                .produce_failover_tx(function_result, Some(error_code))
353                .await
354                .unwrap());
355        }
356
357        // 2. Build and verify the transaction
358        let (tx, request_type, untrusted_verify_idx) =
359            self.build_and_verify_txn(&solana_function_result).await?;
360        let untrusted_verify_ix = &tx.message.instructions[untrusted_verify_idx as usize];
361        let verify_param_bytes = untrusted_verify_ix.data[8..].to_vec();
362        let untrusted_params =
363            FunctionResultValidator::get_params(&request_type, verify_param_bytes.clone())?;
364
365        // 3. Parse the quote
366        let quote_bytes = function_result.quote_bytes();
367        let quote = sgx_quote::Quote::parse(&quote_bytes).map_err(|_| SbError::QuoteParseError)?;
368
369        // 4. Verify the MrEnclave matches the quote
370        if untrusted_params.mr_enclave != quote.isv_report.mrenclave {
371            println!("[QVN] {:?}: mr_enclave mismatch", function_pubkey);
372            // Should we exit here or let it continue and handle the error on-chain?
373            return Err(SbError::MrEnclaveMismatch);
374        }
375
376        // 6. Verify the SGX quote
377        // 6a. Verify the report keyhash matches the enclave generated signer
378        let report_keyhash = &quote.isv_report.report_data[..32];
379        if report_keyhash != Sha256::digest(function_enclave_signer.to_bytes()).as_slice() {
380            println!(
381                "[QVN] [{:?}]: keyhash mismatch: {:?} vs {:?}",
382                function_pubkey,
383                report_keyhash,
384                Sha256::digest(function_enclave_signer.to_bytes()).as_slice()
385            );
386
387            return Err(SbError::FunctionResultFailoverError(
388                200,
389                Arc::new(SbError::FunctionResultIxError("IllegalEnclaveSigner")),
390            ));
391        }
392
393        // 6b. Verify the SGX quote cryptography
394        if !(self.quote_verify_fn)(quote_bytes, untrusted_params.observed_time) {
395            return Err(SbError::FunctionResultFailoverError(
396                201,
397                Arc::new(SbError::FunctionResultError("InvalidQuote")),
398            ));
399        }
400
401        // early exit if the error code is greater than 0, quote is empty, and enclave_signer is null
402
403        // 5. Build trusted verify ixn and compare with untrusted_verify_ixn
404        let trusted_ix = self
405            .build_trusted_verify_ixn(
406                &function_pubkey,
407                &function_enclave_signer,
408                &request_type,
409                &untrusted_params,
410            )
411            .await?;
412        if trusted_ix.data != untrusted_verify_ix.data {
413            println!("[QVN] Left-data: {:?}", trusted_ix.data);
414            println!("[QVN] Right-data: {:?}", untrusted_verify_ix.data);
415            return Err(SbError::FunctionResultFailoverError(
416                200,
417                Arc::new(SbError::FunctionResultIxError(
418                    "IllegalVerifyInstructionData",
419                )),
420            ));
421        }
422        let mut untrusted_accounts = vec![];
423        for account_idx in &untrusted_verify_ix.accounts {
424            if (*account_idx as usize) >= tx.message.account_keys.len() {
425                return Err(SbError::FunctionResultIxError("AccountsMismatch"));
426            }
427            untrusted_accounts.push(tx.message.account_keys[*account_idx as usize]);
428        }
429        let trusted_accounts: Vec<Pubkey> = trusted_ix.accounts.iter().map(|x| x.pubkey).collect();
430
431        // Some verify accounts can be optional where the pubkey is set to the attestation program ID. So we
432        // need to account for that.
433        // TODO: add test case for this
434        if trusted_accounts.len() != untrusted_accounts.len() {
435            println!("[QVN] {}: LEFT: {:#?}", function_pubkey, trusted_accounts);
436            println!(
437                "[QVN] {}: RIGHT: {:#?}",
438                function_pubkey, untrusted_accounts
439            );
440            return Err(SbError::FunctionResultFailoverError(
441                200,
442                Arc::new(SbError::FunctionResultIxError("IllegalVerifyAccounts")),
443            ));
444        }
445        for (i, trusted_account) in trusted_accounts.iter().enumerate() {
446            let untrusted_account = untrusted_accounts.get(i).unwrap();
447            if untrusted_account != trusted_account
448                && untrusted_account != &SWITCHBOARD_ATTESTATION_PROGRAM_ID
449            {
450                println!("[QVN] {}: LEFT: {:#?}", function_pubkey, trusted_accounts);
451                println!(
452                    "[QVN] {}: RIGHT: {:#?}",
453                    function_pubkey, untrusted_accounts
454                );
455                return Err(SbError::FunctionResultFailoverError(
456                    200,
457                    Arc::new(SbError::FunctionResultIxError("IllegalVerifyAccounts")),
458                ));
459            }
460        }
461
462        // 7. Simulate the transaction and build any fail over logic
463        // TODO: should we do this in production? Probably not
464        let replace_blockhash = tx.message.recent_blockhash == Default::default();
465        match self
466            .rpc
467            .simulate_transaction_with_config(
468                &tx,
469                RpcSimulateTransactionConfig {
470                    sig_verify: false,
471                    replace_recent_blockhash: replace_blockhash,
472                    commitment: Some(CommitmentConfig::processed()),
473                    encoding: None,
474                    accounts: None,
475                    min_context_slot: None,
476                },
477            )
478            .await
479        {
480            Ok(resp) => {
481                // println!("[QVN] SimulationResponse: {:?}", resp);
482
483                // TODO: catch common simulation errors and figure out how to convert to our Anchor error
484                if resp.value.err.is_some() {
485                    println!("[QVN] function {} SimulationErrors: {:?}", function_pubkey, resp.value.err.unwrap());
486                    println!("[QVN] function {} SimulationLogs: {:?}", function_pubkey, resp.value.logs.unwrap_or_default());
487
488                    return Err(SbError::FunctionResultFailoverError(
489                        210, // improve the handling here
490                        Arc::new(SbError::Message("UnknownSimulationError")),
491                    ));
492                }
493            }
494            Err(e) => {
495                println!("[QVN] SimulationError: {:?}", e);
496
497                if let Some(TransactionError::InstructionError(idx, e)) = e.get_transaction_error()
498                {
499                    if idx > 0 {
500                        return Err(SbError::FunctionResultFailoverError(
501                            SbFunctionError::CallbackError.as_u8(),
502                            Arc::new(e),
503                        ));
504                    }
505                }
506
507                return Err(SbError::FunctionResultFailoverError(
508                    210, // improve the handling here
509                    Arc::new(e),
510                ));
511            }
512        }
513
514        // 8. Return the partially signed txn that is ready to send
515        // If we avoid signing inside this function, then we do NOT need the verifier keypairs to
516        // validate the function result
517
518        Ok(tx)
519    }
520
521    /// Retrieve the function's SwitchboardWallet from the cache if updated within the timeout, or fetch from on-chain RPC.
522    ///
523    /// # Arguments
524    ///
525    /// * `function_pubkey` - A `Pubkey` for the given function account.
526    ///
527    /// # Returns
528    ///
529    /// Returns a `Result` containing the `Pubkey` of the function's SwitchboardWallet if successful, or an `SbError` if an error occurred.
530    async fn get_function_escrow_wallet(&self, function_pubkey: Pubkey) -> Result<Pubkey, SbError> {
531        if let Some(timeout) = self.cache.timeout {
532            let timeout: i64 = timeout.try_into().unwrap_or_default();
533            self.cache
534                .function_escrow_wallet
535                .remove_if(&function_pubkey, |_k, entry| {
536                    unix_timestamp() - entry.timestamp > timeout
537                });
538        }
539
540        if let Some(function_escrow_cache_entry) =
541            self.cache.function_escrow_wallet.get(&function_pubkey)
542        {
543            return Ok(function_escrow_cache_entry.pubkey);
544        }
545
546        let function_data = FunctionAccountData::fetch_async(&self.rpc, function_pubkey)
547            .await
548            .unwrap();
549
550        self.cache.function_escrow_wallet.insert(
551            function_pubkey,
552            CacheEntry {
553                pubkey: function_data.escrow_wallet,
554                timestamp: unix_timestamp(),
555            },
556        );
557
558        Ok(function_data.escrow_wallet)
559    }
560
561    /// Retrieve the function routine's SwitchboardWallet from the cache if updated within the timeout, or fetch from on-chain RPC.
562    ///
563    /// # Arguments
564    ///
565    /// * `routine_pubkey` - A `Pubkey` for the given function routine account.
566    ///
567    /// # Returns
568    ///
569    /// Returns a `Result` containing the `Pubkey` of the function routine's SwitchboardWallet if successful, or an `SbError` if an error occurred.
570    async fn get_routine_escrow_wallet(&self, routine_pubkey: Pubkey) -> Result<Pubkey, SbError> {
571        if let Some(timeout) = self.cache.timeout {
572            let timeout: i64 = timeout.try_into().unwrap_or_default();
573            self.cache
574                .routine_escrow_wallet
575                .remove_if(&routine_pubkey, |_k, entry| {
576                    unix_timestamp() - entry.timestamp > timeout
577                });
578        }
579
580        if let Some(routine_escrow_cache_entry) =
581            self.cache.routine_escrow_wallet.get(&routine_pubkey)
582        {
583            return Ok(routine_escrow_cache_entry.pubkey);
584        }
585
586        let routine_data = FunctionRoutineAccountData::fetch_async(&self.rpc, routine_pubkey)
587            .await
588            .unwrap();
589
590        self.cache.routine_escrow_wallet.insert(
591            routine_pubkey,
592            CacheEntry {
593                pubkey: routine_data.escrow_wallet,
594                timestamp: unix_timestamp(),
595            },
596        );
597
598        Ok(routine_data.escrow_wallet)
599    }
600
601    /// Sign the transaction with the payer and verifier_enclave_signer keypair and send to the network
602    async fn send_txn(&self, tx: &mut Transaction) -> Result<Signature, SbError> {
603        let verifier_enclave_keypair = self.get_verifier_enclave_signer().await?;
604
605        let recent_blockhash = tx.message.recent_blockhash;
606        let keypairs = &[&*self.payer, &*verifier_enclave_keypair];
607
608        tx.try_partial_sign(keypairs, recent_blockhash).map_err(|e| SbError::CustomError {
609            message: "Failed to sign the Solana transaction with the payer and verifier_enclave_signer keypair".to_string(),
610            source: Arc::new(e),
611        })?;
612
613        match self.rpc.send_transaction(tx).await {
614            Ok(signature) => {
615                println!("[QVN] Sent transaction with signature {:?}", signature);
616                Ok(signature)
617            }
618            Err(e) => {
619                println!("[QVN] Failed to send transaction: {:?}", e);
620                Err(SbError::CustomError {
621                    message: "Failed to send transaction".to_string(),
622                    source: Arc::new(e),
623                })
624            }
625        }
626    }
627
628    async fn build_function_verify_ix(
629        &self,
630        function: Pubkey,
631        enclave_signer: Option<Pubkey>,
632        params: FunctionVerifyParams,
633    ) -> Result<Instruction, SbError> {
634        let verifier_accounts = self.get_verify_accounts().await;
635
636        let function_escrow = self.get_function_escrow_wallet(function).await?;
637
638        let ix = FunctionVerify::build_ix(
639            &(FunctionVerifyAccounts {
640                function,
641                function_enclave_signer: enclave_signer
642                    .unwrap_or(verifier_accounts.verifier_enclave_signer),
643                function_escrow,
644                verifier: verifier_accounts.verifier,
645                verifier_enclave_signer: verifier_accounts.verifier_enclave_signer,
646                reward_receiver: verifier_accounts.reward_receiver,
647                attestation_queue: verifier_accounts.attestation_queue,
648                queue_authority: verifier_accounts.queue_authority,
649            }),
650            &params,
651        )?;
652
653        Ok(ix)
654    }
655
656    async fn build_request_verify_ix(
657        &self,
658        function: Pubkey,
659        request: Pubkey,
660        enclave_signer: Option<Pubkey>,
661        params: FunctionRequestVerifyParams,
662    ) -> Result<Instruction, SbError> {
663        let verifier_accounts = self.get_verify_accounts().await;
664
665        let function_escrow = self.get_function_escrow_wallet(function).await?;
666        let function_escrow_token_wallet =
667            find_associated_token_address(&function_escrow, &NativeMint::ID);
668
669        let ix = FunctionRequestVerify::build_ix(
670            &(FunctionRequestVerifyAccounts {
671                request,
672                function_enclave_signer: enclave_signer
673                    .unwrap_or(verifier_accounts.verifier_enclave_signer),
674                function,
675                function_escrow_token_wallet: Some(function_escrow_token_wallet), // optional
676                verifier: verifier_accounts.verifier,
677                verifier_enclave_signer: verifier_accounts.verifier_enclave_signer,
678                reward_receiver: verifier_accounts.reward_receiver,
679                attestation_queue: verifier_accounts.attestation_queue,
680                queue_authority: verifier_accounts.queue_authority,
681            }),
682            &params,
683        )?;
684
685        Ok(ix)
686    }
687
688    async fn build_routine_verify_ix(
689        &self,
690        function: Pubkey,
691        routine: Pubkey,
692        enclave_signer: Option<Pubkey>,
693        params: FunctionRoutineVerifyParams,
694    ) -> Result<Instruction, SbError> {
695        let verifier_accounts = self.get_verify_accounts().await;
696
697        let function_escrow = self.get_function_escrow_wallet(function).await?;
698        let function_escrow_token_wallet =
699            find_associated_token_address(&function_escrow, &NativeMint::ID);
700
701        let routine_escrow = self.get_routine_escrow_wallet(routine).await?;
702
703        let ix = FunctionRoutineVerify::build_ix(
704            &(FunctionRoutineVerifyAccounts {
705                routine,
706                escrow_wallet: routine_escrow,
707                function_enclave_signer: enclave_signer
708                    .unwrap_or(verifier_accounts.verifier_enclave_signer),
709                function,
710                function_escrow_token_wallet: Some(function_escrow_token_wallet), // optional
711                verifier: verifier_accounts.verifier,
712                verifier_enclave_signer: verifier_accounts.verifier_enclave_signer,
713                reward_receiver: verifier_accounts.reward_receiver,
714                attestation_queue: verifier_accounts.attestation_queue,
715                queue_authority: verifier_accounts.queue_authority,
716            }),
717            &params,
718        )?;
719
720        Ok(ix)
721    }
722
723    /// Produce the oracle failover transaction if the FunctionResult validation returned any errors
724    async fn produce_failover_tx(
725        &self,
726        function_result: &FunctionResult,
727        error_code: Option<u8>,
728    ) -> Result<Transaction, SbError> {
729        let mut function_result = function_result.clone();
730        if let Some(error_code) = error_code {
731            function_result.set_error_code(error_code);
732        }
733
734        let solana_function_result =
735            if let Ok(switchboard_common::ChainResultInfo::Solana(chain_result_info)) =
736                function_result.chain_result_info()
737            {
738                chain_result_info
739            } else {
740                SolanaFunctionResult::default()
741            };
742
743        let function =
744            Pubkey::try_from_slice(function_result.fn_key().unwrap().as_slice()).unwrap();
745
746        let timestamp = unix_timestamp();
747        let next_allowed_timestamp = timestamp + 30;
748
749        let verify_ixn: Instruction = match solana_function_result {
750            // TODO: implement V0 correctly so it can handle function_verify and function_request_verify
751            SolanaFunctionResult::V0(_) => {
752                self.build_function_verify_ix(
753                    function,
754                    None,
755                    FunctionVerifyParams {
756                        observed_time: timestamp,
757                        next_allowed_timestamp,
758                        error_code: function_result.error_code(),
759                        mr_enclave: [0; 32],
760                    },
761                )
762                .await?
763            }
764            SolanaFunctionResult::V1(v) => match v.request_type {
765                SolanaFunctionRequestType::Routine(routine_pubkey_bytes) => {
766                    let routine_pubkey = Pubkey::try_from_slice(&routine_pubkey_bytes[..]).unwrap();
767
768                    self.build_routine_verify_ix(
769                        function,
770                        routine_pubkey,
771                        None,
772                        FunctionRoutineVerifyParams {
773                            mr_enclave: [0; 32],
774                            error_code: function_result.error_code(),
775                            observed_time: timestamp,
776                            next_allowed_timestamp: 0,
777                            container_params_hash: [0u8; 32],
778                        },
779                    )
780                    .await?
781                }
782                SolanaFunctionRequestType::Request(request_pubkey_bytes) => {
783                    let request_pubkey = Pubkey::try_from_slice(&request_pubkey_bytes[..]).unwrap();
784
785                    self.build_request_verify_ix(
786                        function,
787                        request_pubkey,
788                        None,
789                        FunctionRequestVerifyParams {
790                            mr_enclave: [0; 32],
791                            error_code: function_result.error_code(),
792                            observed_time: timestamp,
793                            request_slot: 0,
794                            container_params_hash: [0u8; 32],
795                        },
796                    )
797                    .await?
798                }
799                SolanaFunctionRequestType::Function(_) => {
800                    self.build_function_verify_ix(
801                        function,
802                        None,
803                        FunctionVerifyParams {
804                            observed_time: timestamp,
805                            next_allowed_timestamp,
806                            error_code: function_result.error_code(),
807                            mr_enclave: [0; 32],
808                        },
809                    )
810                    .await?
811                }
812            },
813        };
814
815        let recent_blockhash = self.rpc.get_latest_blockhash().await.unwrap_or_default();
816        // .map_err(|_| SbError::Message("NetworkErr"))?;
817
818        let payer: Pubkey = signer_to_pubkey(self.payer.clone()).unwrap();
819
820        let mut message = Message::new(&[verify_ixn], Some(&payer));
821        message.recent_blockhash = recent_blockhash;
822
823        Ok(Transaction::new_unsigned(message))
824    }
825
826    /// Build the transaction from the emitted serialized_tx Vec<u8> and perform the following validation:
827    /// * Contains at least one instruction
828    /// * First instruction has at least 8 bytes of data
829    /// * First instruction is pointed at the Switchboard Attestation PID
830    /// * First instruction is one of FunctionVerify, FunctionRoutineVerify, or FunctionRequestVerify
831    /// * The verifier's payer and enclave_signer is not used in any other instructions
832    async fn build_and_verify_txn(
833        &self,
834        solana_function_result: &SolanaFunctionResult,
835    ) -> Result<(Transaction, SolanaFunctionRequestType, u8), SbError> {
836        let tx: Transaction = bincode::deserialize(&solana_function_result.serialized_tx())
837            .map_err(|_| {
838                SbError::FunctionResultFailoverError(
839                    200,
840                    Arc::new(SbError::FunctionResultError(
841                        "TransactionDeserializationError",
842                    )),
843                )
844            })?;
845
846        // Verify there is at least one instruction
847        if tx.message.instructions.is_empty() {
848            return Err(SbError::FunctionResultFailoverError(
849                200,
850                Arc::new(SbError::FunctionResultIxError("EmptyInstructions")),
851            ));
852        }
853
854        let untrusted_verify_idx: u8 = 0;
855        let untrusted_verify_ixn = &tx.message.instructions[untrusted_verify_idx as usize];
856
857        if untrusted_verify_ixn.data.len() < 8 {
858            return Err(SbError::FunctionResultFailoverError(
859                200,
860                Arc::new(SbError::FunctionResultIxError("MissingDiscriminator")),
861            ));
862        }
863
864        // Verify the first ixn is pointed at the Switchboard Attestation PID
865        let untrusted_verify_pid_idx = tx
866            .message
867            .account_keys
868            .iter()
869            .position(|&x| x == SWITCHBOARD_ATTESTATION_PROGRAM_ID);
870        if untrusted_verify_pid_idx.is_none()
871            || (untrusted_verify_ixn.program_id_index as usize) != untrusted_verify_pid_idx.unwrap()
872        {
873            return Err(SbError::FunctionResultFailoverError(
874                200,
875                Arc::new(SbError::FunctionResultIxError("InvalidPid")),
876            ));
877        }
878
879        let mut ixn_discriminator = [0u8; 8];
880        ixn_discriminator.copy_from_slice(&untrusted_verify_ixn.data[0..8]);
881
882        let request_type = match solana_function_result {
883            // Derive the request type from the first ixns discriminator if missing
884            SolanaFunctionResult::V0(_) => {
885                (match ixn_discriminator {
886                    FunctionVerify::DISCRIMINATOR => Ok(SolanaFunctionRequestType::Function(
887                        tx.message.account_keys[untrusted_verify_ixn.accounts[0] as usize]
888                            .to_bytes()
889                            .to_vec(),
890                    )),
891                    FunctionRequestVerify::DISCRIMINATOR => Ok(SolanaFunctionRequestType::Request(
892                        tx.message.account_keys[untrusted_verify_ixn.accounts[0] as usize]
893                            .to_bytes()
894                            .to_vec(),
895                    )),
896                    FunctionRoutineVerify::DISCRIMINATOR => Ok(SolanaFunctionRequestType::Routine(
897                        tx.message.account_keys[untrusted_verify_ixn.accounts[0] as usize]
898                            .to_bytes()
899                            .to_vec(),
900                    )),
901                    _ => Err(SbError::FunctionResultFailoverError(
902                        200,
903                        Arc::new(SbError::FunctionResultIxError("InvalidInstructionData")),
904                    )),
905                })?
906            }
907            // 1. Verify the ixn discriminator
908            // 2. Verify the inner pubkey is correctly set
909            SolanaFunctionResult::V1(v1) => match &v1.request_type {
910                SolanaFunctionRequestType::Routine(routine_pubkey_bytes) => {
911                    if ixn_discriminator != FunctionRoutineVerify::DISCRIMINATOR {
912                        return Err(SbError::FunctionResultInvalidData);
913                    }
914                    if routine_pubkey_bytes
915                        != &tx.message.account_keys[untrusted_verify_ixn.accounts[0] as usize]
916                            .to_bytes()
917                            .to_vec()
918                    {
919                        return Err(SbError::FunctionResultInvalidData);
920                    }
921                    v1.request_type.clone()
922                }
923                SolanaFunctionRequestType::Request(request_pubkey_bytes) => {
924                    if ixn_discriminator != FunctionRequestVerify::DISCRIMINATOR {
925                        return Err(SbError::FunctionResultInvalidData);
926                    }
927                    if request_pubkey_bytes
928                        != &tx.message.account_keys[untrusted_verify_ixn.accounts[0] as usize]
929                            .to_bytes()
930                            .to_vec()
931                    {
932                        return Err(SbError::FunctionResultInvalidData);
933                    }
934                    v1.request_type.clone()
935                }
936                SolanaFunctionRequestType::Function(function_pubkey_bytes) => {
937                    if ixn_discriminator != FunctionVerify::DISCRIMINATOR {
938                        return Err(SbError::FunctionResultInvalidData);
939                    }
940                    if function_pubkey_bytes
941                        != &tx.message.account_keys[untrusted_verify_ixn.accounts[0] as usize]
942                            .to_bytes()
943                            .to_vec()
944                    {
945                        return Err(SbError::FunctionResultInvalidData);
946                    }
947                    v1.request_type.clone()
948                }
949            },
950        };
951
952        let compute_budget_pid = solana_sdk::compute_budget::ID;
953        // @ottersec double check this is safe
954        let feeds_pid = SWITCHBOARD_PROGRAM_ID;
955        let functions_pid = SWITCHBOARD_ATTESTATION_PROGRAM_ID;
956        // validate the verifier_enclave_signer and payer are not used in any downstream ixns
957        if tx.message.instructions.len() > 1 {
958            let verifier_enclave_signer = *self.get_verifier_enclave_pubkey().await;
959            let enclave_signer_idx = tx
960                .message
961                .account_keys
962                .iter()
963                .position(|&x| x == verifier_enclave_signer);
964            let payer_idx = tx
965                .message
966                .account_keys
967                .iter()
968                .position(|&x| x == self.payer.pubkey());
969            let mut start_idx = 1;
970            loop {
971                if start_idx >= tx.message.instructions.len() {
972                    break;
973                }
974                let pid = tx.message.account_keys[tx.message.instructions[start_idx].program_id_index as usize];
975                if pid == compute_budget_pid || pid == feeds_pid || pid == functions_pid {
976                    start_idx += 1;
977                } else {
978                    break;
979                }
980            }
981            for ix in &tx.message.instructions[start_idx..] {
982                for account_idx in &ix.accounts {
983                    if Some(*account_idx as usize) == enclave_signer_idx {
984                        return Err(SbError::FunctionResultIllegalAccount);
985                    }
986                    if Some(*account_idx as usize) == payer_idx {
987                        return Err(SbError::FunctionResultIllegalAccount);
988                    }
989                }
990            }
991        }
992
993        Ok((tx, request_type, untrusted_verify_idx))
994    }
995
996    /// Deserialize the verify instructions parameters and return a cleaned up version of the params based on the request type
997    fn get_params(
998        request_type: &SolanaFunctionRequestType,
999        verify_param_bytes: Vec<u8>,
1000    ) -> Result<FunctionValidatorVerifyParams, SbError> {
1001        match request_type {
1002            SolanaFunctionRequestType::Routine(_) => {
1003                let params =
1004                    FunctionRoutineVerifyParams::deserialize(&mut verify_param_bytes.as_slice())
1005                        .map_err(|_e| {
1006                            SbError::FunctionResultFailoverError(
1007                                200,
1008                                Arc::new(SbError::FunctionResultIxError("InvalidInstructionData")),
1009                            )
1010                        })?;
1011
1012                Ok(FunctionValidatorVerifyParams {
1013                    mr_enclave: params.mr_enclave,
1014                    error_code: params.error_code,
1015                    observed_time: params.observed_time,
1016                    container_params_hash: params.container_params_hash,
1017                    next_allowed_timestamp: params.next_allowed_timestamp,
1018                    ..Default::default()
1019                })
1020            }
1021            SolanaFunctionRequestType::Request(_) => {
1022                let params =
1023                    FunctionRequestVerifyParams::deserialize(&mut verify_param_bytes.as_slice())
1024                        .map_err(|_e| {
1025                            SbError::FunctionResultFailoverError(
1026                                200,
1027                                Arc::new(SbError::FunctionResultIxError("InvalidInstructionData")),
1028                            )
1029                        })?;
1030
1031                Ok(FunctionValidatorVerifyParams {
1032                    mr_enclave: params.mr_enclave,
1033                    error_code: params.error_code,
1034                    observed_time: params.observed_time,
1035                    container_params_hash: params.container_params_hash,
1036                    request_slot: params.request_slot,
1037                    ..Default::default()
1038                })
1039            }
1040            SolanaFunctionRequestType::Function(_) => {
1041                let params = FunctionVerifyParams::deserialize(&mut verify_param_bytes.as_slice())
1042                    .map_err(|_e| {
1043                        SbError::FunctionResultFailoverError(
1044                            200,
1045                            Arc::new(SbError::FunctionResultIxError("InvalidInstructionData")),
1046                        )
1047                    })?;
1048
1049                Ok(FunctionValidatorVerifyParams {
1050                    mr_enclave: params.mr_enclave,
1051                    error_code: params.error_code,
1052                    observed_time: params.observed_time,
1053                    next_allowed_timestamp: params.next_allowed_timestamp,
1054                    ..Default::default()
1055                })
1056            }
1057        }
1058    }
1059
1060    /// Return the accounts used by this verifier to verify the function result
1061    async fn get_verify_accounts(&self) -> FunctionResultValidatorAccounts {
1062        FunctionResultValidatorAccounts {
1063            verifier: *self.verifier,
1064            verifier_enclave_signer: *self.get_verifier_enclave_pubkey().await,
1065            payer: self.payer.pubkey(),
1066            reward_receiver: *self.reward_receiver,
1067            attestation_queue: *self.attestation_queue,
1068            queue_authority: *self.queue_authority,
1069        }
1070    }
1071
1072    /// Build a new version of the verify ixn for basic sanity checking
1073    async fn build_trusted_verify_ixn(
1074        &self,
1075        function_pubkey: &Pubkey,
1076        function_enclave_signer: &Pubkey,
1077        request_type: &SolanaFunctionRequestType,
1078        untrusted_params: &FunctionValidatorVerifyParams,
1079    ) -> Result<Instruction, SbError> {
1080        let trusted_verify_ixn: Instruction = match &request_type {
1081            SolanaFunctionRequestType::Routine(routine_pubkey_bytes) => {
1082                self.build_routine_verify_ix(
1083                    *function_pubkey,
1084                    Pubkey::try_from_slice(&routine_pubkey_bytes[..]).unwrap(),
1085                    Some(*function_enclave_signer),
1086                    FunctionRoutineVerifyParams {
1087                        observed_time: untrusted_params.observed_time,
1088                        next_allowed_timestamp: untrusted_params.next_allowed_timestamp,
1089                        error_code: untrusted_params.error_code,
1090                        mr_enclave: untrusted_params.mr_enclave,
1091                        container_params_hash: untrusted_params.container_params_hash,
1092                    },
1093                )
1094                .await?
1095            }
1096            SolanaFunctionRequestType::Request(request_pubkey_bytes) => {
1097                self.build_request_verify_ix(
1098                    *function_pubkey,
1099                    Pubkey::try_from_slice(&request_pubkey_bytes[..]).unwrap(),
1100                    Some(*function_enclave_signer),
1101                    FunctionRequestVerifyParams {
1102                        mr_enclave: untrusted_params.mr_enclave,
1103                        error_code: untrusted_params.error_code,
1104                        observed_time: untrusted_params.observed_time,
1105                        container_params_hash: untrusted_params.container_params_hash,
1106
1107                        request_slot: untrusted_params.request_slot,
1108                    },
1109                )
1110                .await?
1111            }
1112            SolanaFunctionRequestType::Function(_) => {
1113                self.build_function_verify_ix(
1114                    *function_pubkey,
1115                    Some(*function_enclave_signer),
1116                    FunctionVerifyParams {
1117                        mr_enclave: untrusted_params.mr_enclave,
1118                        error_code: untrusted_params.error_code,
1119                        observed_time: untrusted_params.observed_time,
1120                        next_allowed_timestamp: untrusted_params.next_allowed_timestamp,
1121                    },
1122                )
1123                .await?
1124            }
1125        };
1126
1127        Ok(trusted_verify_ixn)
1128    }
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133    use super::*;
1134    use std::str::FromStr;
1135    use tokio::sync::OnceCell;
1136
1137    // lazy loaded validator, having trouble using this with anchor28 - runtime crashing after async closure exits
1138    static VALIDATOR: OnceCell<FunctionResultValidator> = OnceCell::const_new();
1139    static FUNCTION: OnceCell<Pubkey> = OnceCell::const_new();
1140
1141    // 6vLX2GC3FQ6HtXe5K2b3CYePToB7bdCHQs6nPEFwg6bH
1142    const DEMO_FUNCTION_PUBKEY_BYTES: [u8; 32] = [
1143        87, 244, 73, 65, 67, 23, 129, 192, 3, 231, 155, 123, 4, 35, 131, 151, 109, 104, 41, 161,
1144        81, 238, 54, 71, 208, 241, 158, 58, 108, 158, 156, 240,
1145    ];
1146
1147    /// Build a buffer from a valid quote and replace the mrenclave and report data with the provided params
1148    fn build_quote_buffer(enclave_signer: &Keypair, mrenclave: [u8; 32]) -> Vec<u8> {
1149        let mut quote_buffer = [0u8; 1456];
1150
1151        // First we copy from a valid quote so we start with some good data
1152        let raw_sgx_quote_bytes = include_bytes!("../../fixtures/v2_quote.bin");
1153        quote_buffer[0..1456].copy_from_slice(&raw_sgx_quote_bytes[0..1456]);
1154
1155        // Set the mrenclave
1156        quote_buffer[112..144].copy_from_slice(&mrenclave);
1157
1158        // Set the report data
1159        quote_buffer[368..400]
1160            .copy_from_slice(Sha256::digest(enclave_signer.pubkey().to_bytes()).as_slice());
1161
1162        // TODO: sign the report and add the signature so we can fully verify the quote if we need to
1163
1164        quote_buffer.to_vec()
1165    }
1166
1167    // Async getter for our OnceLocked validator
1168    async fn get_devnet_function_result_validator() -> &'static FunctionResultValidator {
1169        VALIDATOR.get_or_init(|| async {
1170            // We dont do any signature verification inside the validate function so these can be dummy keypairs
1171
1172            let home_dir = dirs::home_dir().expect("Could not find the home directory");
1173            let keypair_path = home_dir.join(".config/solana/id.json"); // Build the path
1174
1175            let payer = load_keypair_fs(keypair_path.to_str().unwrap()).unwrap();
1176
1177            // Devnet verifier oracle
1178            // let accounts = FunctionResultValidatorInitAccounts {
1179            //     verifier_pubkey: Pubkey::from_str("FT41PAvhJj7YqQwuALeSr2PDh7kEub2wb9Ve64jPjDXk")
1180            //         .unwrap(),
1181            //     attestation_queue: Pubkey::from_str("CkvizjVnm2zA5Wuwan34NhVT3zFc7vqUyGnA6tuEF5aE")
1182            //         .unwrap(),
1183            //     queue_authority: Pubkey::from_str("2KgowxogBrGqRcgXQEmqFvC3PGtCu66qERNJevYW8Ajh")
1184            //         .unwrap(),
1185            //     reward_receiver: Pubkey::from_str("CRXGEGMz4RoRyjXhktMp7SzkcFLV3uevZcr2yCnnWpBt")
1186            //         .unwrap(),
1187            // };
1188
1189            // TODO: load this using the env variable RPC_URL
1190            let cluster = Cluster::from_str(
1191                "https://switchbo-switchbo-6225.devnet.rpcpool.com/f6fb9f02-0777-498b-b8f5-67cbb1fc0d14"
1192            ).unwrap_or(Cluster::Devnet);
1193
1194            let client = AnchorClient::new_with_options(
1195                cluster,
1196                payer.clone(),
1197                CommitmentConfig::processed()
1198            );
1199            let program = get_attestation_program(&client).unwrap();
1200
1201            let rpc = Arc::new(program.async_rpc());
1202
1203            let bootstrapped_queue = BootstrappedAttestationQueue::get_or_create_from_seed(
1204                &rpc,
1205                payer.clone(),
1206                None,
1207                None
1208            ).await.unwrap();
1209            let accounts = FunctionResultValidatorInitAccounts {
1210                verifier: bootstrapped_queue.verifier,
1211                attestation_queue: bootstrapped_queue.attestation_queue,
1212                queue_authority: bootstrapped_queue.queue_authority,
1213                reward_receiver: Pubkey::from_str(
1214                    "CRXGEGMz4RoRyjXhktMp7SzkcFLV3uevZcr2yCnnWpBt"
1215                ).unwrap(),
1216            };
1217
1218            let function_pubkey = Pubkey::from(DEMO_FUNCTION_PUBKEY_BYTES);
1219
1220            // let verifier_data = VerifierAccountData::fetch(&program.rpc(), accounts.verifier_pubkey).unwrap();
1221            // let function_data = FunctionAccountData::fetch(&program.rpc(), function_pubkey).unwrap();
1222
1223            // let (verifier_data_result, function_data_result) = tokio::join!(
1224            //     VerifierAccountData::fetch_async(&rpc, accounts.verifier_pubkey),
1225            //     FunctionAccountData::fetch_async(&rpc, function_pubkey)
1226            // );
1227
1228            // let verifier_data = verifier_data_result.unwrap();
1229            // let function_data = function_data_result.unwrap();
1230
1231            let verifier_data = VerifierAccountData::fetch_async(
1232                &rpc,
1233                accounts.verifier
1234            ).await.unwrap();
1235            let function_data = FunctionAccountData::fetch_async(
1236                &rpc,
1237                function_pubkey
1238            ).await.unwrap();
1239
1240            let cache = FunctionResultValidatorCache {
1241                timeout: None,
1242                function_escrow_wallet: Arc::new(DashMap::with_capacity(10_000)),
1243                routine_escrow_wallet: Arc::new(DashMap::with_capacity(10_000)),
1244            };
1245
1246            cache.function_escrow_wallet.insert(function_pubkey, CacheEntry {
1247                pubkey: function_data.escrow_wallet,
1248                timestamp: unix_timestamp(),
1249            });
1250
1251            FunctionResultValidator::new(
1252                Arc::new(RwLock::new(client)),
1253                rpc,
1254                payer.clone(),
1255                FunctionResultValidatorSigner::Simulation(
1256                    Arc::new(verifier_data.enclave.enclave_signer)
1257                ),
1258                &accounts,
1259                |_quote_bytes, _observed_time| true,
1260                Some(cache)
1261            )
1262        }).await
1263    }
1264
1265    /// Async getter for our dedicated function pubkey
1266    async fn get_function_pubkey() -> &'static Pubkey {
1267        FUNCTION
1268            .get_or_init(|| async {
1269                let validator = get_devnet_function_result_validator().await;
1270
1271                let function_pubkey = FunctionAccountData::get_or_create_from_seed(
1272                    &validator.rpc,
1273                    validator.payer.clone(),
1274                    *validator.attestation_queue,
1275                    None,
1276                    None,
1277                )
1278                .await
1279                .unwrap();
1280
1281                // let function_pubkey = Pubkey::from(DEMO_FUNCTION_PUBKEY_BYTES);
1282                function_pubkey
1283            })
1284            .await
1285    }
1286
1287    /// Initialize the logger, the validator, and request an airdrop if the payer is out of funds
1288    async fn setup_test_validator() -> &'static FunctionResultValidator {
1289        let validator = get_devnet_function_result_validator().await;
1290
1291        let payer_balance = validator
1292            .rpc
1293            .get_balance(&validator.payer.pubkey())
1294            .await
1295            .unwrap();
1296
1297        if payer_balance == 0 {
1298            let sig = validator
1299                .rpc
1300                .request_airdrop(&validator.payer.pubkey(), 1_000_000_000)
1301                .await
1302                .unwrap();
1303            println!("[Payer] Airdrop requested. Txn Signature: {}", sig);
1304        }
1305
1306        validator
1307    }
1308
1309    #[tokio::test]
1310    async fn test_function_validation() {
1311        // Setup logging
1312        if let Err(_e) = std::env::var("RUST_LOG") {
1313            std::env::set_var("RUST_LOG", "debug");
1314        }
1315
1316        match json_env_logger::try_init() {
1317            Ok(_) => {
1318                println!("Logger initialized");
1319            }
1320            Err(e) => {
1321                println!("Failed to initialize logger: {:?}", e);
1322            }
1323        }
1324
1325        let validator = setup_test_validator().await;
1326
1327        let function_enclave_signer = Keypair::new();
1328
1329        let sgx_quote_bytes =
1330            build_quote_buffer(&function_enclave_signer, DEFAULT_FUNCTION_MR_ENCLAVE);
1331        let _sgx_quote = sgx_quote::Quote::parse(&sgx_quote_bytes[..]).unwrap();
1332
1333        // let mut mr_enclave = [0u8; 32];
1334        // mr_enclave.copy_from_slice(sgx_quote.isv_report.mrenclave);
1335
1336        // Function
1337        let function_pubkey = *get_function_pubkey().await;
1338
1339        let timestamp = unix_timestamp();
1340        let next_allowed_timestamp = timestamp + 30;
1341
1342        let function_verify_ix = validator
1343            .build_function_verify_ix(
1344                function_pubkey,
1345                Some(function_enclave_signer.pubkey()),
1346                FunctionVerifyParams {
1347                    mr_enclave: DEFAULT_FUNCTION_MR_ENCLAVE,
1348                    error_code: 0,
1349                    observed_time: timestamp,
1350                    next_allowed_timestamp,
1351                },
1352            )
1353            .await
1354            .unwrap();
1355
1356        let ixs = vec![function_verify_ix];
1357        let message = Message::new(&ixs, Some(&validator.payer.pubkey()));
1358        let blockhash = validator
1359            .rpc
1360            .get_latest_blockhash()
1361            .await
1362            .unwrap_or_default();
1363        let mut tx = solana_sdk::transaction::Transaction::new_unsigned(message);
1364        tx.partial_sign(&[&function_enclave_signer], blockhash);
1365        let serialized_tx = bincode::serialize(&tx).unwrap();
1366
1367        let function_result = FunctionResult::V1(FunctionResultV1 {
1368            quote: sgx_quote_bytes.to_vec(),
1369            signer: function_enclave_signer.pubkey().to_bytes().to_vec(),
1370            signature: vec![],
1371            chain_result_info: ChainResultInfo::Solana(SolanaFunctionResult::V1(
1372                SolanaFunctionResultV1 {
1373                    serialized_tx,
1374                    fn_key: function_pubkey.to_bytes().to_vec(),
1375                    request_type: SolanaFunctionRequestType::Function(
1376                        function_pubkey.to_bytes().to_vec(),
1377                    ),
1378                    request_hash: [0u8; 32].to_vec(),
1379                },
1380            )),
1381            error_code: 0,
1382        });
1383
1384        let result = validator.validate(&function_result).await;
1385        // println!("[Function] Result: {:?}", result);
1386        assert!(result.is_ok());
1387    }
1388
1389    #[tokio::test]
1390    async fn test_request_validation() {
1391        // Setup logging
1392        if let Err(_e) = std::env::var("RUST_LOG") {
1393            std::env::set_var("RUST_LOG", "debug");
1394        }
1395
1396        match json_env_logger::try_init() {
1397            Ok(_) => {
1398                println!("Logger initialized");
1399            }
1400            Err(e) => {
1401                println!("Failed to initialize logger: {:?}", e);
1402            }
1403        }
1404
1405        let validator = setup_test_validator().await;
1406
1407        let function_enclave_signer = Keypair::new();
1408
1409        let sgx_quote_bytes =
1410            build_quote_buffer(&function_enclave_signer, DEFAULT_FUNCTION_MR_ENCLAVE);
1411        let _sgx_quote = sgx_quote::Quote::parse(&sgx_quote_bytes[..]).unwrap();
1412
1413        // let mut mr_enclave = [0u8; 32];
1414        // mr_enclave.copy_from_slice(sgx_quote.isv_report.mrenclave);
1415
1416        // Function
1417        let function_pubkey = *get_function_pubkey().await;
1418
1419        let request_pubkey = FunctionRequestAccountData::get_or_create_from_seed(
1420            &validator.rpc,
1421            validator.payer.clone(),
1422            function_pubkey,
1423            None,
1424            None,
1425        )
1426        .await
1427        .unwrap();
1428
1429        // Meh, using devnet causes the oracles to respond which breaks the tests.
1430        // Need a dedicated queue for this.
1431        let request_data = FunctionRequestAccountData::fetch_async(&validator.rpc, request_pubkey)
1432            .await
1433            .unwrap();
1434
1435        let verify_ix = validator
1436            .build_request_verify_ix(
1437                function_pubkey,
1438                request_pubkey,
1439                Some(function_enclave_signer.pubkey()),
1440                FunctionRequestVerifyParams {
1441                    mr_enclave: DEFAULT_FUNCTION_MR_ENCLAVE,
1442                    error_code: 0,
1443                    observed_time: unix_timestamp(),
1444                    request_slot: request_data.active_request.request_slot,
1445                    container_params_hash: request_data.container_params_hash,
1446                },
1447            )
1448            .await
1449            .unwrap();
1450
1451        let ixs = vec![verify_ix];
1452        let message = Message::new(&ixs, Some(&validator.payer.pubkey()));
1453        let blockhash = validator
1454            .rpc
1455            .get_latest_blockhash()
1456            .await
1457            .unwrap_or_default();
1458        let mut tx = solana_sdk::transaction::Transaction::new_unsigned(message);
1459        tx.partial_sign(&[&function_enclave_signer], blockhash);
1460        let serialized_tx = bincode::serialize(&tx).unwrap();
1461
1462        let function_result = FunctionResult::V1(FunctionResultV1 {
1463            quote: sgx_quote_bytes.to_vec(),
1464            signer: function_enclave_signer.pubkey().to_bytes().to_vec(),
1465            signature: vec![],
1466            chain_result_info: ChainResultInfo::Solana(SolanaFunctionResult::V1(
1467                SolanaFunctionResultV1 {
1468                    serialized_tx,
1469                    fn_key: function_pubkey.to_bytes().to_vec(),
1470                    request_type: SolanaFunctionRequestType::Request(
1471                        request_pubkey.to_bytes().to_vec(),
1472                    ),
1473                    request_hash: [0u8; 32].to_vec(),
1474                },
1475            )),
1476            error_code: 0,
1477        });
1478
1479        let result = validator.validate(&function_result).await;
1480        // println!("[Request] Result: {:?}", result);
1481        assert!(result.is_ok());
1482    }
1483}
1484
1485fn parse_mrenclave_hex(hex_str: &str) -> Result<[u8; 32], SbError> {
1486    let mut mrenclave = [0u8; 32];
1487
1488    let hex_bytes = hex::decode(hex_str).map_err(|_e| SbError::Message("InvalidHex"))?;
1489    if hex_bytes.len() != 32 {
1490        return Err(SbError::Message("InvalidHex"));
1491    }
1492
1493    mrenclave.copy_from_slice(&hex_bytes);
1494    Ok(mrenclave)
1495}
1496
1497async fn print_function_verify_accounts(function_verify_ix: Instruction, rpc: Arc<RpcClient>) {
1498    let verify_ix_accounts: Vec<Pubkey> = function_verify_ix
1499        .accounts
1500        .clone()
1501        .iter()
1502        .map(|a| a.pubkey)
1503        .collect();
1504
1505    println!(
1506        "#1 {:<24}: {:?}",
1507        "Function",
1508        verify_ix_accounts.get(0).unwrap()
1509    );
1510    println!(
1511        "#2 {:<24}: {:?}",
1512        "FunctionEnclaveSigner",
1513        verify_ix_accounts.get(1).unwrap()
1514    );
1515    println!(
1516        "#3 {:<24}: {:?}",
1517        "Verifier",
1518        verify_ix_accounts.get(2).unwrap()
1519    );
1520    println!(
1521        "#4 {:<24}: {:?}",
1522        "VerifierEnclaveSigner",
1523        verify_ix_accounts.get(3).unwrap()
1524    );
1525    println!(
1526        "#5 {:<24}: {:?}",
1527        "VerifierPermission",
1528        verify_ix_accounts.get(4).unwrap()
1529    );
1530    println!(
1531        "#6 {:<24}: {:?}",
1532        "EscrowWallet",
1533        verify_ix_accounts.get(5).unwrap()
1534    );
1535    println!(
1536        "#7 {:<24}: {:?}",
1537        "EscrowTokenWallet",
1538        verify_ix_accounts.get(6).unwrap()
1539    );
1540    println!(
1541        "#8 {:<24}: {:?}",
1542        "Receiver",
1543        verify_ix_accounts.get(7).unwrap()
1544    );
1545    println!(
1546        "#9 {:<24}: {:?}",
1547        "Attestation Queue",
1548        verify_ix_accounts.get(8).unwrap()
1549    );
1550
1551    let account_infos = rpc
1552        .get_multiple_accounts(&verify_ix_accounts)
1553        .await
1554        .unwrap();
1555
1556    for (i, account) in account_infos.iter().enumerate() {
1557        // #2 and #4 are enclave generated keypairs and can be skipped
1558        if account.is_none() && i != 1 && i != 3 {
1559            println!("Account #{} is missing", i + 1);
1560        }
1561    }
1562}
1563
1564// TODO: Create a way to initialize a BoostrappedAttestationQueue using the payer's secret key,
1565// some base seed, and a hash of the parameters. This will allow us to create a new queue for each
1566// config and easily build getter methods to retrieve them.