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