This commit is contained in:
Conner Gallagher 2023-06-14 00:14:23 -06:00
parent 6673c29746
commit d2d711dd29
12 changed files with 340 additions and 325 deletions

View File

@ -12,7 +12,7 @@ pub struct AttestationQueueAccountData {
// Authority controls adding/removing allowed enclave measurements
pub authority: Pubkey,
// allowed enclave measurements
pub mr_enclaves: [[u8; 32]; 32],
pub mr_enclaves: [MrEnclave; 32],
pub mr_enclaves_len: u32,
pub data: [Pubkey; 128],
pub data_len: u32,

View File

@ -1,6 +1,6 @@
use super::QuoteAccountData;
use crate::cfg_client;
use crate::prelude::*;
use crate::{cfg_client, QuoteAccountData, SWITCHBOARD_ATTESTATION_PROGRAM_ID};
use anchor_lang::{Discriminator, Owner, ZeroCopy};
use bytemuck::{Pod, Zeroable};
use std::cell::Ref;

View File

@ -5,6 +5,8 @@ use std::cell::Ref;
use crate::{QUOTE_SEED, SWITCHBOARD_ATTESTATION_PROGRAM_ID};
pub type MrEnclave = [u8; 32];
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum VerificationStatus {
@ -31,7 +33,7 @@ pub struct QuoteAccountData {
/// Queue used for attestation to verify a MRENCLAVE measurement.
pub attestation_queue: Pubkey,
/// The quotes MRENCLAVE measurement dictating the contents of the secure enclave.
pub mr_enclave: [u8; 32],
pub mr_enclave: MrEnclave,
pub verification_status: u8,
pub verification_timestamp: i64,
pub valid_until: i64,

View File

@ -1,8 +1,4 @@
use crate::prelude::*;
use anchor_lang::solana_program::entrypoint::ProgramResult;
use anchor_lang::solana_program::instruction::Instruction;
use anchor_lang::solana_program::program::{invoke, invoke_signed};
use anchor_lang::{Discriminator, InstructionData};
#[derive(Accounts)]
#[instruction(params: FunctionTriggerParams)] // rpc parameters hint

View File

@ -1,10 +1,4 @@
use crate::cfg_client;
use crate::prelude::*;
use anchor_lang::solana_program::entrypoint::ProgramResult;
use anchor_lang::solana_program::instruction::Instruction;
use anchor_lang::solana_program::program::{invoke, invoke_signed};
use anchor_lang::{Discriminator, InstructionData};
use anchor_spl::token::TokenAccount;
#[derive(Accounts)]
#[instruction(params: FunctionVerifyParams)] // rpc parameters hint
@ -201,203 +195,4 @@ impl<'info> FunctionVerify<'info> {
},
]
}
cfg_client! {
pub async fn build(
client: &solana_client::rpc_client::RpcClient,
fn_signer: std::sync::Arc<solana_sdk::signer::keypair::Keypair>,
pubkeys: &FunctionVerifyPubkeys,
mr_enclave: [u8; 32],
) -> std::result::Result<Instruction, switchboard_common::error::Error> {
let fn_signer_pubkey = crate::client::to_pubkey(fn_signer)?;
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let fn_data: FunctionAccountData = load_account(&client, pubkeys.function).await?;
let verifier_quote: QuoteAccountData = load_account(&client, pubkeys.verifier).await?;
let queue_data: AttestationQueueAccountData =
crate::client::load_account(&client, fn_data.attestation_queue).await?;
// let escrow = fn_data.escrow;
let (fn_quote, _) = Pubkey::find_program_address(
&[b"QuoteAccountData", &pubkeys.function.to_bytes()],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
let (verifier_permission, _) = Pubkey::find_program_address(
&[
b"PermissionAccountData",
&queue_data.authority.to_bytes(),
&fn_data.attestation_queue.to_bytes(),
&pubkeys.payer.to_bytes(),
],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
let (fn_permission, _) = Pubkey::find_program_address(
&[
b"PermissionAccountData",
&queue_data.authority.to_bytes(),
&fn_data.attestation_queue.to_bytes(),
&pubkeys.function.to_bytes(),
],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
let (state, _) =
Pubkey::find_program_address(&[b"STATE"], &SWITCHBOARD_ATTESTATION_PROGRAM_ID);
let accounts = FunctionVerifyAccounts {
function: pubkeys.function,
fn_signer: fn_signer_pubkey,
fn_quote,
verifier_quote: pubkeys.verifier,
secured_signer: verifier_quote.authority,
attestation_queue: fn_data.attestation_queue,
escrow: fn_data.escrow,
receiver: pubkeys.reward_receiver,
verifier_permission,
fn_permission,
state,
token_program: anchor_spl::token::ID,
payer: pubkeys.payer,
system_program: anchor_lang::solana_program::system_program::ID,
};
let next_allowed_timestamp = fn_data
.next_execution_timestamp()
.map(|x| x.timestamp())
.unwrap_or(i64::MAX);
Ok(Self::build_ix(
accounts,
current_time,
next_allowed_timestamp,
false,
mr_enclave,
))
}
fn build_ix(
accounts: FunctionVerifyAccounts,
observed_time: i64,
next_allowed_timestamp: i64,
is_failure: bool,
mr_enclave: [u8; 32],
) -> Instruction {
Instruction {
program_id: SWITCHBOARD_ATTESTATION_PROGRAM_ID,
accounts: accounts.to_account_metas(None),
data: FunctionVerifyParams {
observed_time,
next_allowed_timestamp,
is_failure,
mr_enclave,
}
.data(),
}
}
}
}
cfg_client! {
pub struct FunctionVerifyAccounts {
pub function: Pubkey,
pub fn_signer: Pubkey,
pub fn_quote: Pubkey,
pub verifier_quote: Pubkey,
pub secured_signer: Pubkey,
pub attestation_queue: Pubkey,
pub escrow: Pubkey,
pub receiver: Pubkey,
pub verifier_permission: Pubkey,
pub fn_permission: Pubkey,
pub state: Pubkey,
pub token_program: Pubkey,
pub payer: Pubkey,
pub system_program: Pubkey,
}
impl ToAccountMetas for FunctionVerifyAccounts {
fn to_account_metas(&self, _: Option<bool>) -> Vec<AccountMeta> {
vec![
AccountMeta {
pubkey: self.function,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.fn_signer,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: self.fn_quote,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.verifier_quote,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.secured_signer,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: self.attestation_queue,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.escrow,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.receiver,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.verifier_permission,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.fn_permission,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.state,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.token_program,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.payer,
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: self.system_program,
is_signer: false,
is_writable: false,
},
]
}
}
}

View File

@ -0,0 +1,296 @@
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 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::sync::Arc;
#[derive(Clone)]
pub struct FunctionRunner {
pub client: Arc<RpcClient>,
signer_keypair: Arc<Keypair>,
pub signer: Pubkey,
pub function: Pubkey,
pub quote: Pubkey,
pub payer: Pubkey,
pub verifier: Pubkey,
pub reward_receiver: 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.to_string(),
self.verifier.to_string()
)
}
}
impl FunctionRunner {
pub fn new_with_client(client: RpcClient) -> Result<Self, SwitchboardClientError> {
let signer_keypair = generate_signer();
let signer = signer_to_pubkey(signer_keypair.clone())?;
let function = load_env_pubkey("FUNCTION_KEY")?;
let payer = load_env_pubkey("PAYER")?;
let verifier = load_env_pubkey("VERIFIER")?;
let reward_receiver = load_env_pubkey("REWARD_RECEIVER")?;
let (quote, _bump) = Pubkey::find_program_address(
&[QUOTE_SEED, function.as_ref()],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
Ok(Self {
client: Arc::new(client),
signer_keypair,
signer,
function,
quote,
payer,
verifier,
reward_receiver,
})
}
pub fn new(
url: &str,
commitment: Option<CommitmentConfig>,
) -> Result<Self, SwitchboardClientError> {
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, SwitchboardClientError> {
Self::new(cluster.url(), commitment)
}
async fn build_verify_ixn(
&self,
mr_enclave: MrEnclave,
) -> Result<Instruction, SwitchboardClientError> {
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let fn_data: FunctionAccountData = load_account(&self.client, self.function).await?;
let verifier_quote: QuoteAccountData = load_account(&self.client, self.verifier).await?;
let queue_data: AttestationQueueAccountData =
crate::client::load_account(&self.client, fn_data.attestation_queue).await?;
// let escrow = fn_data.escrow;
let (fn_quote, _) = Pubkey::find_program_address(
&[b"QuoteAccountData", &self.function.to_bytes()],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
let (verifier_permission, _) = Pubkey::find_program_address(
&[
b"PermissionAccountData",
&queue_data.authority.to_bytes(),
&fn_data.attestation_queue.to_bytes(),
&self.payer.to_bytes(), // assuming the verifier payer is also the authority
],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
let (fn_permission, _) = Pubkey::find_program_address(
&[
b"PermissionAccountData",
&queue_data.authority.to_bytes(),
&fn_data.attestation_queue.to_bytes(),
&self.function.to_bytes(),
],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
let (state, _) =
Pubkey::find_program_address(&[b"STATE"], &SWITCHBOARD_ATTESTATION_PROGRAM_ID);
let pubkeys = FunctionVerifyPubkeys {
function: self.function,
fn_signer: self.signer,
fn_quote,
verifier_quote: self.verifier,
secured_signer: verifier_quote.authority,
attestation_queue: fn_data.attestation_queue,
escrow: fn_data.escrow,
receiver: self.reward_receiver,
verifier_permission,
fn_permission,
state,
token_program: anchor_spl::token::ID,
payer: self.payer,
system_program: anchor_lang::solana_program::system_program::ID,
};
let next_allowed_timestamp = fn_data
.next_execution_timestamp()
.map(|x| x.timestamp())
.unwrap_or(i64::MAX);
let ixn_params = FunctionVerifyParams {
observed_time: current_time,
next_allowed_timestamp,
is_failure: false,
mr_enclave,
};
let ixn = Instruction {
program_id: SWITCHBOARD_ATTESTATION_PROGRAM_ID,
accounts: pubkeys.to_account_metas(None),
data: ixn_params.data(),
};
Ok(ixn)
}
async fn get_result(
&self,
mut ixs: Vec<Instruction>,
) -> Result<FunctionResult, SwitchboardClientError> {
let quote_raw = Gramine::generate_quote(&self.signer.to_bytes()).unwrap();
let quote = Quote::parse(&quote_raw).unwrap();
let mr_enclave: MrEnclave = quote.isv_report.mrenclave.try_into().unwrap();
let verify_ixn = self.build_verify_ixn(mr_enclave).await?;
ixs.insert(0, verify_ixn);
let message = Message::new(&ixs, Some(&self.payer));
let blockhash = self.client.get_latest_blockhash().unwrap();
let mut tx = solana_sdk::transaction::Transaction::new_unsigned(message);
tx.partial_sign(&[self.signer_keypair.as_ref()], blockhash);
Ok(FunctionResult {
version: 1,
chain: switchboard_common::Chain::Solana,
key: self.function.to_bytes(),
signer: self.signer.to_bytes(),
serialized_tx: bincode::serialize(&tx).unwrap(),
quote: quote_raw,
..Default::default()
})
}
pub async fn emit(&self, ixs: Vec<Instruction>) -> Result<(), SwitchboardClientError> {
self.get_result(ixs)
.await
.map_err(|e| SwitchboardClientError::CustomError {
message: "failed to run function verify".to_string(),
source: Box::new(e),
})
.unwrap()
.emit();
Ok(())
}
}
pub struct FunctionVerifyPubkeys {
pub function: Pubkey,
pub fn_signer: Pubkey,
pub fn_quote: Pubkey,
pub verifier_quote: Pubkey,
pub secured_signer: Pubkey,
pub attestation_queue: Pubkey,
pub escrow: Pubkey,
pub receiver: Pubkey,
pub verifier_permission: Pubkey,
pub fn_permission: Pubkey,
pub state: Pubkey,
pub token_program: Pubkey,
pub payer: Pubkey,
pub system_program: Pubkey,
}
impl ToAccountMetas for FunctionVerifyPubkeys {
fn to_account_metas(&self, _: Option<bool>) -> Vec<AccountMeta> {
vec![
AccountMeta {
pubkey: self.function,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.fn_signer,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: self.fn_quote,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.verifier_quote,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.secured_signer,
is_signer: true,
is_writable: false,
},
AccountMeta {
pubkey: self.attestation_queue,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.escrow,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.receiver,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.verifier_permission,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.fn_permission,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.state,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.token_program,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: self.payer,
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: self.system_program,
is_signer: false,
is_writable: false,
},
]
}
}

View File

@ -1,100 +0,0 @@
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 sgx_quote::Quote;
use solana_sdk::signer::keypair::Keypair;
use std::env;
use std::result::Result;
use std::str::FromStr;
use std::sync::Arc;
use crate::attestation_program::FunctionVerify;
use crate::{QUOTE_SEED, SWITCHBOARD_ATTESTATION_PROGRAM_ID};
pub async fn function_verify(
url: String,
fn_signer: Arc<Keypair>,
mut ixs: Vec<Instruction>,
) -> Result<switchboard_common::FunctionResult, switchboard_common::Error> {
let fn_signer_pubkey = crate::client::to_pubkey(fn_signer.clone())?;
let client = solana_client::rpc_client::RpcClient::new_with_commitment(
url,
solana_sdk::commitment_config::CommitmentConfig::processed(),
);
let quote_raw =
switchboard_common::Gramine::generate_quote(&fn_signer_pubkey.to_bytes()).unwrap();
let quote = Quote::parse(&quote_raw).unwrap();
let pubkeys = FunctionVerifyPubkeys::load_from_env()?;
let ix = FunctionVerify::build(
&client,
fn_signer.clone(),
&pubkeys,
quote.isv_report.mrenclave.try_into().unwrap(),
)
.await?;
ixs.insert(0, ix);
let message = Message::new(&ixs, Some(&pubkeys.payer));
let blockhash = client.get_latest_blockhash().unwrap();
let mut tx = solana_sdk::transaction::Transaction::new_unsigned(message);
tx.partial_sign(&[fn_signer.as_ref()], blockhash);
Ok(switchboard_common::FunctionResult {
version: 1,
chain: switchboard_common::Chain::Solana,
key: pubkeys.function.to_bytes(),
signer: fn_signer_pubkey.to_bytes(),
serialized_tx: bincode::serialize(&tx).unwrap(),
quote: quote_raw,
..Default::default()
})
}
pub struct FunctionVerifyPubkeys {
pub function: Pubkey,
pub quote: Pubkey,
pub payer: Pubkey,
pub verifier: Pubkey,
pub reward_receiver: Pubkey,
}
impl FunctionVerifyPubkeys {
pub fn load_from_env() -> std::result::Result<Self, switchboard_common::Error> {
let function = Pubkey::from_str(&env::var("FUNCTION_KEY").unwrap()).unwrap();
let payer = Pubkey::from_str(&env::var("PAYER").unwrap()).unwrap();
let verifier = &env::var("VERIFIER").unwrap_or(String::new());
if verifier.is_empty() {
return Err(switchboard_common::Error::CustomMessage(
"verifier missing".to_string(),
));
}
let (quote, _bump) = Pubkey::find_program_address(
&[QUOTE_SEED, function.as_ref()],
&SWITCHBOARD_ATTESTATION_PROGRAM_ID,
);
Ok(Self {
function,
quote,
payer,
verifier: Pubkey::from_str(verifier).map_err(|_| {
switchboard_common::Error::CustomMessage(
"failed to parse pubkey string".to_string(),
)
})?,
reward_receiver: Pubkey::from_str(&env::var("REWARD_RECEIVER").unwrap()).unwrap(),
})
}
}

View File

@ -1,5 +1,5 @@
pub mod function_verify;
pub mod function_runner;
pub mod utils;
pub use function_verify::*;
pub use function_runner::*;
pub use utils::*;

View File

@ -1,8 +1,15 @@
use crate::prelude::*;
use solana_sdk::signer::keypair::{keypair_from_seed, Keypair};
use std::env;
use std::result::Result;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
pub fn load_env_pubkey(key: &str) -> Result<Pubkey, SwitchboardClientError> {
Pubkey::from_str(&env::var(key).unwrap())
.map_err(|_| SwitchboardClientError::EnvVariableMissing(key.to_string()))
}
pub fn generate_signer() -> Arc<Keypair> {
let mut randomness = [0; 32];
@ -10,9 +17,11 @@ pub fn generate_signer() -> Arc<Keypair> {
Arc::new(keypair_from_seed(&randomness).unwrap())
}
pub fn to_pubkey(signer: Arc<Keypair>) -> std::result::Result<Pubkey, switchboard_common::Error> {
pub fn signer_to_pubkey(
signer: Arc<Keypair>,
) -> std::result::Result<Pubkey, SwitchboardClientError> {
let pubkey = Pubkey::from_str(signer.to_base58_string().as_str()).map_err(|_| {
switchboard_common::Error::CustomMessage("failed to parse pubkey string".to_string())
SwitchboardClientError::CustomMessage("failed to parse pubkey string".to_string())
})?;
Ok(pubkey)
}
@ -20,10 +29,19 @@ pub fn to_pubkey(signer: Arc<Keypair>) -> std::result::Result<Pubkey, switchboar
pub async fn load_account<T: bytemuck::Pod>(
client: &solana_client::rpc_client::RpcClient,
pubkey: Pubkey,
) -> Result<T, switchboard_common::Error> {
) -> Result<T, SwitchboardClientError> {
let data = client
.get_account_data(&pubkey)
.map_err(|_| switchboard_common::Error::CustomMessage("AnchorParseError".to_string()))?;
.map_err(|_| SwitchboardClientError::CustomMessage("AnchorParseError".to_string()))?;
Ok(*bytemuck::try_from_bytes::<T>(&data[8..])
.map_err(|_| switchboard_common::Error::CustomMessage("AnchorParseError".to_string()))?)
.map_err(|_| SwitchboardClientError::CustomMessage("AnchorParseError".to_string()))?)
}
pub fn unix_timestamp() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
.try_into()
.unwrap_or(0)
}

View File

@ -10,8 +10,6 @@ pub use oracle_program::*;
pub mod attestation_program;
pub use attestation_program::*;
pub use switchboard_common::{Chain, Error as SwitchboardClientError, FunctionResult};
pub mod error;
pub mod seeds;

View File

@ -17,6 +17,8 @@ pub use rust_decimal;
cfg_client! {
pub use crate::client::*;
pub use switchboard_common::Gramine;
pub use anchor_client;
pub use anchor_client::anchor_lang;
pub use anchor_client::solana_client;
@ -24,6 +26,8 @@ cfg_client! {
pub use anchor_client::anchor_lang::solana_program;
pub use anchor_client::Cluster;
pub use solana_sdk::signer::keypair::{keypair_from_seed, Keypair};
pub use anchor_lang::prelude::*;
}
@ -35,4 +39,10 @@ cfg_program! {
}
pub use anchor_lang::prelude::*;
pub use anchor_lang::{Discriminator, Owner, ZeroCopy};
pub use anchor_lang::{
AnchorDeserialize, AnchorSerialize, Discriminator, InstructionData, Owner, ZeroCopy,
};
pub use anchor_spl::token::{Mint, TokenAccount};
pub use solana_program::entrypoint::ProgramResult;
pub use solana_program::instruction::Instruction;
pub use solana_program::program::{invoke, invoke_signed};

View File

@ -8,6 +8,6 @@ pub use crate::oracle_program::{
};
pub use crate::attestation_program::{
FunctionStatus, FunctionTriggerParams, FunctionVerifyParams, SwitchboardAttestationPermission,
VerificationStatus,
FunctionStatus, FunctionTriggerParams, FunctionVerifyParams, MrEnclave,
SwitchboardAttestationPermission, VerificationStatus,
};