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 Success(String), SwitchboardError(String, u8), Fallback(String, u8), }
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#[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#[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 pub request_slot: u64,
75 pub next_allowed_timestamp: i64,
76}
77
78#[derive(Clone)]
80pub struct FunctionResultValidator {
81 pub client: Arc<RwLock<AnchorClient>>,
82 pub rpc: Arc<RpcClient>,
83 pub payer: Arc<Keypair>,
84
85 pub verifier: Arc<Pubkey>,
87 pub verifier_enclave_signer: FunctionResultValidatorSigner,
88 pub reward_receiver: Arc<Pubkey>,
89
90 pub attestation_queue: Arc<Pubkey>,
92 pub queue_authority: Arc<Pubkey>,
93
94 pub quote_verify_fn: Arc<Box<QuoteVerifyFn>>,
95
96 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 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_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 pub fn is_production(&self) -> bool {
231 matches!(
232 &self.verifier_enclave_signer,
233 FunctionResultValidatorSigner::Production(_)
234 )
235 }
236
237 pub fn is_simulation(&self) -> bool {
239 matches!(
240 &self.verifier_enclave_signer,
241 FunctionResultValidatorSigner::Simulation(_)
242 )
243 }
244
245 async fn get_verifier_enclave_signer(&self) -> Result<Arc<Keypair>, SbError> {
247 match &self.verifier_enclave_signer {
248 FunctionResultValidatorSigner::Production(keypair) => {
249 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 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 (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 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) }
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 pub async fn validate(&self, function_result: &FunctionResult) -> Result<Transaction, SbError> {
329 let error_code = function_result.error_code();
330 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 error_code >= 200 {
350 return Ok(self
351 .produce_failover_tx(function_result, Some(error_code))
352 .await
353 .unwrap());
354 }
355
356 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 let quote_bytes = function_result.quote_bytes();
366 let quote = sgx_quote::Quote::parse("e_bytes).map_err(|_| SbError::QuoteParseError)?;
367
368 if untrusted_params.mr_enclave != quote.isv_report.mrenclave {
370 println!("[QVN] {:?}: mr_enclave mismatch", function_pubkey);
371 return Err(SbError::MrEnclaveMismatch);
373 }
374
375 let report_keyhash = "e.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 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 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 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 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 if resp.value.err.is_some() {
484 println!("[QVN] SimulationErrors: {:?}", resp.value.err.unwrap());
485
486 return Err(SbError::FunctionResultFailoverError(
487 210, 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, Arc::new(e),
508 ));
509 }
510 }
511
512 Ok(tx)
517 }
518
519 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 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 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 ¶ms,
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), 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 ¶ms,
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), 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 ¶ms,
716 )?;
717
718 Ok(ix)
719 }
720
721 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 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 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 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 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 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 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 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 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 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 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 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 static VALIDATOR: OnceCell<FunctionResultValidator> = OnceCell::const_new();
1121 static FUNCTION: OnceCell<Pubkey> = OnceCell::const_new();
1122
1123 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 fn build_quote_buffer(enclave_signer: &Keypair, mrenclave: [u8; 32]) -> Vec<u8> {
1131 let mut quote_buffer = [0u8; 1456];
1132
1133 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 quote_buffer[112..144].copy_from_slice(&mrenclave);
1139
1140 quote_buffer[368..400]
1142 .copy_from_slice(Sha256::digest(enclave_signer.pubkey().to_bytes()).as_slice());
1143
1144 quote_buffer.to_vec()
1147 }
1148
1149 async fn get_devnet_function_result_validator() -> &'static FunctionResultValidator {
1151 VALIDATOR.get_or_init(|| async {
1152 let home_dir = dirs::home_dir().expect("Could not find the home directory");
1155 let keypair_path = home_dir.join(".config/solana/id.json"); let payer = load_keypair_fs(keypair_path.to_str().unwrap()).unwrap();
1158
1159 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_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 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 function_pubkey
1265 })
1266 .await
1267 }
1268
1269 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 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 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 assert!(result.is_ok());
1369 }
1370
1371 #[tokio::test]
1372 async fn test_request_validation() {
1373 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 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 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 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 if account.is_none() && i != 1 && i != 3 {
1541 println!("Account #{} is missing", i + 1);
1542 }
1543 }
1544}
1545
1546