use crate::prelude::*;
use anchor_lang::solana_program::instruction::Instruction;
use anchor_lang::solana_program::message::Message;
use anchor_lang::solana_program::pubkey::Pubkey;
use anchor_lang::AnchorDeserialize;
use sgx_quote::Quote;
use solana_client::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::signer::keypair::Keypair;
use std::result::Result;
use std::str::FromStr;
use std::sync::Arc;
use switchboard_common::SolanaFunctionEnvironment;
use switchboard_common::{ChainResultInfo::Solana, SolanaFunctionResult};
#[derive(Clone)]
pub struct FunctionRunner {
pub request_type: SolanaFunctionRequestType,
pub client: Arc<RpcClient>,
signer_keypair: Arc<Keypair>,
pub signer: Pubkey,
pub function: Pubkey,
pub payer: Pubkey,
pub verifier: Pubkey,
pub reward_receiver: Pubkey,
pub function_data: Option<Box<FunctionAccountData>>,
pub verifier_enclave_signer: Option<Pubkey>,
pub queue_authority: Option<Pubkey>,
pub function_routine_key: Option<Pubkey>,
pub function_routine_data: Option<Box<FunctionRoutineAccountData>>,
pub function_request_key: Option<Pubkey>,
pub function_request_data: Option<Box<FunctionRequestAccountData>>,
pub attestation_queue: Option<Pubkey>,
pub switchboard_state: Pubkey,
pub switchboard: Pubkey,
}
impl std::fmt::Display for FunctionRunner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SwitchboardFunctionRunner: url: {}, signer: {}, function: {}, verifier: {}",
self.client.url(),
self.signer,
self.function,
self.verifier,
)
}
}
impl FunctionRunner {
pub fn new_with_client(client: RpcClient) -> Result<Self, SbError> {
let signer_keypair = generate_signer();
let signer = signer_to_pubkey(signer_keypair.clone())?;
let env = SolanaFunctionEnvironment::parse()?;
let function = Pubkey::from_str(&env.function_key).unwrap();
let payer = Pubkey::from_str(&env.payer).unwrap();
let verifier = Pubkey::from_str(&env.verifier).unwrap();
let reward_receiver = Pubkey::from_str(&env.reward_receiver).unwrap();
let mut attestation_queue: Option<Pubkey> = None;
let fn_hex = env.function_data;
let fn_raw = hex::decode(fn_hex).unwrap();
let function_data = Some(Box::new(*bytemuck::try_from_bytes::<FunctionAccountData>(&fn_raw).unwrap()));
attestation_queue = Some(function_data.as_ref().unwrap().attestation_queue);
let verifier_enclave_signer: Option<Pubkey> =
parse_optional_pubkey(&env.verifier_enclave_signer);
let queue_authority: Option<Pubkey> = parse_optional_pubkey(&env.queue_authority);
let function_routine_key: Option<Pubkey> = parse_optional_pubkey(&env.function_routine_key);
let function_routine_data: Option<Box<FunctionRoutineAccountData>> =
if env.function_routine_data.is_empty() {
None
} else {
match FunctionRoutineAccountData::try_from_slice(
&hex::decode(&env.function_routine_data).unwrap_or_default(),
) {
Ok(function_routine_data) => {
if attestation_queue.is_none() {
attestation_queue = Some(function_routine_data.attestation_queue);
}
Some(Box::new(function_routine_data))
}
Err(_) => None,
}
};
let function_request_key: Option<Pubkey> = parse_optional_pubkey(&env.function_request_key);
let function_request_data: Option<Box<FunctionRequestAccountData>> =
if env.function_request_data.is_empty() {
None
} else {
match FunctionRequestAccountData::try_from_slice(
&hex::decode(&env.function_request_data).unwrap_or_default(),
) {
Ok(function_request_data) => {
if attestation_queue.is_none() {
attestation_queue = Some(function_request_data.attestation_queue);
}
Some(Box::new(function_request_data))
}
Err(_) => None,
}
};
let switchboard: Pubkey =
load_env_pubkey("SWITCHBOARD").unwrap_or(SWITCHBOARD_ATTESTATION_PROGRAM_ID);
let switchboard_state = AttestationProgramState::get_program_pda(Some(switchboard));
let request_type: SolanaFunctionRequestType =
if let Some(function_routine_key) = function_routine_key {
SolanaFunctionRequestType::Routine(function_routine_key.to_bytes().into())
} else if let Some(function_request_key) = function_request_key {
SolanaFunctionRequestType::Request(function_request_key.to_bytes().into())
} else {
SolanaFunctionRequestType::Function(function.to_bytes().into())
};
Ok(Self {
request_type,
client: Arc::new(client),
signer_keypair,
signer,
function,
function_data,
function_routine_key,
function_routine_data,
function_request_key,
function_request_data,
payer,
verifier,
reward_receiver,
verifier_enclave_signer,
queue_authority,
attestation_queue,
switchboard,
switchboard_state,
})
}
pub fn new(url: &str, commitment: Option<CommitmentConfig>) -> Result<Self, SbError> {
Self::new_with_client(RpcClient::new_with_commitment(
url,
commitment.unwrap_or_default(),
))
}
pub fn new_from_cluster(
cluster: Cluster,
commitment: Option<CommitmentConfig>,
) -> Result<Self, SbError> {
Self::new(cluster.url(), commitment)
}
pub fn from_env(commitment: Option<CommitmentConfig>) -> Result<Self, SbError> {
let cluster =
Cluster::from_str(&std::env::var("CLUSTER").unwrap_or("mainnet".to_string())).unwrap();
Self::new_from_cluster(cluster, commitment)
}
pub fn is_scheduled_function(&self) -> bool {
matches!(self.request_type, SolanaFunctionRequestType::Function(_))
}
pub fn is_routine(&self) -> bool {
matches!(self.request_type, SolanaFunctionRequestType::Routine(_))
}
pub fn is_request(&self) -> bool {
matches!(self.request_type, SolanaFunctionRequestType::Request(_))
}
pub async fn load_params(&mut self) -> Result<Vec<u8>, SbError> {
match self.request_type {
SolanaFunctionRequestType::Routine(_) => {
let routine = self.load_routine_data().await?;
let params = routine.container_params.clone();
if self.function_routine_data.is_none() {
self.function_routine_data = Some(routine);
}
Ok(params)
}
SolanaFunctionRequestType::Request(_) => {
let request = self.load_request_data().await?;
let params = request.container_params.clone();
if self.function_request_data.is_none() {
self.function_request_data = Some(request);
}
Ok(params)
}
SolanaFunctionRequestType::Function(_) => {
let function_data = self.load_function_data().await?;
let params = function_data.default_container_params.to_vec();
if self.function_data.is_none() {
self.function_data = Some(function_data);
}
Ok(params)
}
}
}
pub async fn load_serde_params<T: serde::de::DeserializeOwned>(
&mut self,
) -> Result<T, SbError> {
let bytes: Vec<u8> = self.load_params().await?;
let decoded: T = serde_json::from_slice(&bytes[..])?;
Ok(decoded)
}
pub fn assert_mr_enclave(&self) -> Result<(), SbError> {
if let Some(function_data) = self.function_data.clone() {
let quote_raw = Gramine::generate_quote(&self.signer.to_bytes()).unwrap_or_default();
if let Ok(quote) = Quote::parse("e_raw) {
let mr_enclave: MrEnclave = quote.isv_report.mrenclave.try_into().unwrap();
if !function_data.mr_enclaves.contains(&mr_enclave) {
return Err(SbError::MrEnclaveMismatch);
}
}
}
Ok(())
}
async fn load_queue_authority(
&self,
attestation_queue_pubkey: Pubkey,
) -> Result<Pubkey, SbError> {
let queue_authority = self.queue_authority.unwrap_or_default();
if queue_authority != Pubkey::default() {
return Ok(queue_authority);
}
msg!(
"QUEUE_AUTHORITY is missing - Fetching attestation_queue account {}",
attestation_queue_pubkey
);
match AttestationQueueAccountData::fetch(&self.client, attestation_queue_pubkey) {
Err(error) => Err(SbError::CustomMessage(format!(
"failed to fetch attestation_queue {}: {}",
attestation_queue_pubkey, error
))),
Ok(attestation_queue) => Ok(attestation_queue.authority),
}
}
pub fn get_associated_token_address(owner: Pubkey, mint: Option<Pubkey>) -> Pubkey {
anchor_spl::associated_token::get_associated_token_address(
&owner,
&mint.unwrap_or(anchor_spl::token::spl_token::native_mint::ID),
)
}
async fn load_verifier_signer(&self, verifier_pubkey: Pubkey) -> Result<Pubkey, SbError> {
let verifier_enclave_signer = self.verifier_enclave_signer.unwrap_or_default();
if verifier_enclave_signer != Pubkey::default() {
return Ok(verifier_enclave_signer);
}
msg!(
"VERIFIER_ENCLAVE_SIGNER is missing - Fetching verifier account {}",
verifier_pubkey
);
match VerifierAccountData::fetch(&self.client, verifier_pubkey) {
Err(error) => Err(SbError::CustomMessage(format!(
"failed to fetch verifier {}: {}",
verifier_pubkey, error
))),
Ok(verifier_data) => Ok(verifier_data.enclave.enclave_signer),
}
}
async fn build_verify_ixn(
&self,
mr_enclave: [u8; 32],
error_code: u8,
) -> Result<Instruction, SbError> {
match self.request_type {
SolanaFunctionRequestType::Routine(_) => {
self.build_fn_routine_verify_ixn(mr_enclave, error_code)
.await
}
SolanaFunctionRequestType::Request(_) => {
self.build_fn_request_verify_ixn(mr_enclave, error_code)
.await
}
SolanaFunctionRequestType::Function(_) => {
self.build_fn_verify_ixn(mr_enclave, error_code).await
}
}
}
pub async fn get_function_result(
&self,
mut ixs: Vec<Instruction>,
error_code: u8,
) -> Result<FunctionResult, SbError> {
let quote_raw = Gramine::generate_quote(&self.signer.to_bytes()).unwrap_or_default();
let mut mr_enclave: MrEnclave = Default::default();
if quote_raw.is_empty() {
println!("WARNING!: Reading from enclave failed! If you are testing, this is expected behavior.");
} else {
let quote = Quote::parse("e_raw).unwrap();
mr_enclave = quote.isv_report.mrenclave.try_into().unwrap();
}
let verify_ixn: Instruction = self.build_verify_ixn(mr_enclave, error_code).await?;
ixs.insert(0, verify_ixn);
let message = Message::new(&ixs, Some(&self.payer));
let blockhash = self.client.get_latest_blockhash().unwrap_or_default();
let mut tx = solana_sdk::transaction::Transaction::new_unsigned(message);
tx.partial_sign(&[self.signer_keypair.as_ref()], blockhash);
let serialized_tx = bincode::serialize(&tx).unwrap();
let request_hash = if self.is_request() {
solana_program::hash::hash(
&self
.function_request_data
.as_ref()
.unwrap()
.container_params,
)
.to_bytes()
.to_vec()
} else if self.is_routine() {
solana_program::hash::hash(
&self
.function_routine_data
.as_ref()
.unwrap()
.container_params,
)
.to_bytes()
.to_vec()
} else {
let function_data = self.load_function_data().await?;
solana_program::hash::hash(&function_data.as_ref().default_container_params)
.to_bytes()
.to_vec()
};
Ok(FunctionResult::V1(FunctionResultV1 {
quote: quote_raw,
signer: self.signer.to_bytes().into(),
signature: vec![],
chain_result_info: Solana(SolanaFunctionResult::V1(SolanaFunctionResultV1 {
fn_key: self.function.to_bytes().into(),
serialized_tx,
request_hash,
request_type: self.request_type.clone(),
})),
error_code,
}))
}
pub async fn emit(&self, ixs: Vec<Instruction>) -> Result<(), SbError> {
self.get_function_result(ixs, 0)
.await
.map_err(|e| SbError::CustomMessage(format!("failed to get verify ixn: {}", e)))
.unwrap()
.emit();
Ok(())
}
pub async fn emit_error(&self, error_code: u8) -> Result<(), SbError> {
self.get_function_result(vec![], error_code)
.await
.unwrap()
.emit();
Ok(())
}
pub async fn load_function_data(&self) -> Result<Box<FunctionAccountData>, SbError> {
if let Some(function_data) = self.function_data.as_ref() {
if **function_data != FunctionAccountData::default() {
return Ok(function_data.clone());
}
}
msg!(
"FUNCTION_DATA is missing - Fetching function account {}",
self.function
);
FunctionAccountData::fetch(&self.client, self.function)
.map_err(|error| {
SbError::CustomMessage(format!(
"failed to fetch function {}: {}",
self.function, error
))
})
.and_then(|function_data| {
if function_data != FunctionAccountData::default() {
Ok(Box::new(function_data))
} else {
Err(SbError::CustomMessage(format!(
"function account {} is empty",
self.function
)))
}
})
}
pub async fn load_accounts(&mut self) -> Result<(), SbError> {
if self.is_scheduled_function() {
self.load_function().await?;
}
if self.is_routine() {
self.load_routine().await?;
}
if self.is_request() {
self.load_request().await?;
}
Ok(())
}
pub async fn load_function(&mut self) -> Result<(Pubkey, Box<FunctionAccountData>), SbError> {
if self.function == Pubkey::default() {
return Err(SbError::Message("Failed to load the function pubkey"));
}
let function_data = self.load_function_data().await?;
self.function_data = Some(function_data.clone());
Ok((self.function, function_data))
}
async fn build_fn_verify_ixn(
&self,
mr_enclave: MrEnclave,
error_code: u8,
) -> Result<Instruction, SbError> {
if self.function == Pubkey::default() {
return Err(SbError::CustomMessage(
"funciton pubkey is missing but required to build function_verify ixn".to_string(),
));
}
let function_data = self.load_function_data().await?;
let queue_authority = self
.load_queue_authority(function_data.attestation_queue)
.await?;
let verifier_enclave_signer = self.load_verifier_signer(self.verifier).await?;
let next_allowed_timestamp = match function_data.get_next_execution_datetime() {
Some(next_allowed_timestamp) => next_allowed_timestamp.timestamp(),
None => i64::MAX,
};
let ixn = FunctionVerify::build_ix(
&FunctionVerifyAccounts {
function: self.function,
function_enclave_signer: self.signer,
function_escrow: function_data.escrow_wallet,
verifier: self.verifier,
verifier_enclave_signer,
reward_receiver: self.reward_receiver,
attestation_queue: function_data.attestation_queue,
queue_authority,
},
&FunctionVerifyParams {
observed_time: unix_timestamp(),
next_allowed_timestamp,
error_code,
mr_enclave,
},
)?;
Ok(ixn)
}
pub async fn load_routine_data(&self) -> Result<Box<FunctionRoutineAccountData>, SbError> {
let function_routine_key = self.function_routine_key.unwrap_or_default();
if function_routine_key == Pubkey::default() {
return Err(SbError::CustomMessage(
"function_routine_key is missing but required to fetch function routine account"
.to_string(),
));
}
if let Some(function_routine_data) = self.function_routine_data.as_ref() {
if **function_routine_data != FunctionRoutineAccountData::default() {
return Ok(function_routine_data.clone());
}
}
msg!(
"FUNCTION_ROUTINE_DATA is missing - Fetching function_routine account {}",
function_routine_key
);
match FunctionRoutineAccountData::fetch(&self.client, function_routine_key) {
Ok(function_routine_data) => Ok(Box::new(function_routine_data)),
Err(error) => Err(SbError::CustomMessage(format!(
"failed to fetch function routine {}: {}",
function_routine_key, error
))),
}
}
pub async fn load_routine(
&mut self,
) -> Result<(Pubkey, Box<FunctionRoutineAccountData>), SbError> {
let routine_pubkey = self.function_routine_key.unwrap_or_default();
if routine_pubkey == Pubkey::default() {
return Err(SbError::Message("Failed to load the routine pubkey"));
}
let routine_data = self.load_routine_data().await?;
self.function_routine_data = Some(routine_data.clone());
Ok((routine_pubkey, routine_data))
}
async fn build_fn_routine_verify_ixn(
&self,
mr_enclave: MrEnclave,
error_code: u8,
) -> Result<Instruction, SbError> {
if self.function_routine_data.is_none() || self.function_routine_key.is_none() {
return Err(SbError::CustomMessage(
"function_routine_verify instruction needs routine environment present."
.to_string(),
));
}
let function_routine_data = self.function_routine_data.clone().unwrap_or_default();
let function_routine_key = self.function_routine_key.unwrap_or_default();
if function_routine_data.function != self.function {
return Err(SbError::CustomMessage(format!(
"function_key mismatch: expected {}, received {}",
function_routine_data.function, self.function
)));
}
let function_data = self.load_function_data().await?;
let queue_authority = self
.load_queue_authority(function_data.attestation_queue)
.await?;
let verifier_enclave_signer = self.load_verifier_signer(self.verifier).await?;
let next_allowed_timestamp = match function_data.get_next_execution_datetime() {
Some(next_allowed_timestamp) => next_allowed_timestamp.timestamp(),
None => i64::MAX,
};
let container_params_hash =
solana_program::hash::hash(&function_routine_data.container_params).to_bytes();
let function_escrow_token_wallet = if function_data.routines_dev_fee > 0 {
Some(function_data.escrow_token_wallet)
} else {
None
};
let ixn = FunctionRoutineVerify::build_ix(
&FunctionRoutineVerifyAccounts {
routine: function_routine_key,
function_enclave_signer: self.signer,
escrow_wallet: function_data.escrow_wallet,
function: self.function,
function_escrow_token_wallet,
verifier: self.verifier,
verifier_enclave_signer,
reward_receiver: self.reward_receiver,
attestation_queue: function_data.attestation_queue,
queue_authority,
},
&FunctionRoutineVerifyParams {
observed_time: unix_timestamp(),
next_allowed_timestamp,
error_code,
mr_enclave,
container_params_hash,
},
)?;
Ok(ixn)
}
pub async fn load_request_data(&self) -> Result<Box<FunctionRequestAccountData>, SbError> {
let function_request_key = self.function_request_key.unwrap_or_default();
if function_request_key == Pubkey::default() {
return Err(SbError::CustomMessage(
"function_request_key is missing but required to fetch function request account"
.to_string(),
));
}
if let Some(function_request_data) = self.function_request_data.as_ref() {
if **function_request_data != FunctionRequestAccountData::default() {
return Ok(function_request_data.clone());
}
}
msg!(
"FUNCTION_REQUEST_DATA is missing - Fetching function_request account {}",
function_request_key
);
match FunctionRequestAccountData::fetch(&self.client, function_request_key) {
Ok(function_request_data) => Ok(Box::new(function_request_data)),
Err(error) => Err(SbError::CustomMessage(format!(
"failed to fetch function request {}: {}",
function_request_key, error
))),
}
}
pub async fn load_request(
&mut self,
) -> Result<(Pubkey, Box<FunctionRequestAccountData>), SbError> {
let request_pubkey = self.function_request_key.unwrap_or_default();
if request_pubkey == Pubkey::default() {
return Err(SbError::Message("Failed to load the request pubkey"));
}
let request_data = self.load_request_data().await?;
self.function_request_data = Some(request_data.clone());
Ok((request_pubkey, request_data))
}
async fn build_fn_request_verify_ixn(
&self,
mr_enclave: MrEnclave,
error_code: u8,
) -> Result<Instruction, SbError> {
if self.function_request_data.is_none() || self.function_request_key.is_none() {
return Err(SbError::CustomMessage(
"function_request_verify instruction needs request environment present."
.to_string(),
));
}
let function_request_data = self.function_request_data.clone().unwrap_or_default();
let function_request_key = self.function_request_key.unwrap_or_default();
if function_request_data.function != self.function {
return Err(SbError::CustomMessage(format!(
"function_key mismatch: expected {}, received {}",
function_request_data.function, self.function
)));
}
let function_data = self.load_function_data().await?;
let queue_authority = self
.load_queue_authority(function_data.attestation_queue)
.await?;
let verifier_enclave_signer = self.load_verifier_signer(self.verifier).await?;
let container_params_hash =
solana_program::hash::hash(&function_request_data.container_params).to_bytes();
let function_escrow_token_wallet = if function_data.routines_dev_fee > 0 {
Some(function_data.escrow_token_wallet)
} else {
None
};
let ixn = FunctionRequestVerify::build_ix(
&FunctionRequestVerifyAccounts {
request: function_request_key,
function_enclave_signer: self.signer,
function: self.function,
function_escrow_token_wallet,
verifier: self.verifier,
verifier_enclave_signer,
reward_receiver: self.reward_receiver,
attestation_queue: function_data.attestation_queue,
queue_authority,
},
&FunctionRequestVerifyParams {
observed_time: unix_timestamp(),
error_code,
mr_enclave,
request_slot: function_request_data.active_request.request_slot,
container_params_hash,
},
)?;
Ok(ixn)
}
}