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