Lines
0 %
Functions
Branches
100 %
use crate::common::*;
use anyhow::Result;
use bonsol_prover::input_resolver::{DefaultInputResolver, InputResolver, ProgramInput};
use bonsol_sdk::instructions::{ExecutionConfig, InputRef};
use bonsol_sdk::{BonsolClient, ExecutionAccountStatus, InputType};
use indicatif::ProgressBar;
use sha2::{Digest, Sha256};
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::bs58;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signer::Signer;
use std::fs::File;
use std::sync::Arc;
use tokio::time::Instant;
pub async fn execution_waiter(
sdk: &BonsolClient,
requester: Pubkey,
execution_id: String,
expiry: u64,
timeout: Option<u64>,
) -> Result<()> {
let indicator = ProgressBar::new_spinner();
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(1));
let now = Instant::now();
loop {
if let Some(timeout) = timeout {
if now.elapsed().as_secs() > timeout {
return Err(anyhow::anyhow!("Timeout"));
}
interval.tick().await;
let current_block = sdk.get_current_slot().await?;
indicator.set_message(format!(
"Waiting for execution to be claimed, current block {} expiry {}",
current_block, expiry
));
if current_block > expiry {
indicator.finish_with_message("Execution expired");
return Err(anyhow::anyhow!("Execution expired"));
let claim_state = sdk.get_claim_state_v1(&requester, &execution_id).await;
if let Ok(claim_state) = claim_state {
let claim = claim_state.claim()?;
indicator.finish_with_message(format!(
"Claimed by {} at slot {}, committed {}",
bs58::encode(claim.claimer).into_string(),
claim.claimed_at,
claim.block_commitment
break;
//now we are looking for execution request finished
indicator.finish_with_message("Execution timed out");
let exec_status = sdk
.get_execution_request_v1(&requester, &execution_id)
.await?;
match exec_status {
ExecutionAccountStatus::Completed(ec) => {
indicator.finish_with_message(format!("Execution completed with exit code {}", ec));
return Ok(());
ExecutionAccountStatus::Pending(_) => {
indicator.tick();
continue;
pub async fn execute(
rpc_url: String,
keypair: impl Signer,
execution_request_file: Option<String>,
image_id: Option<String>,
execution_id: Option<String>,
inputs_file: Option<String>,
tip: Option<u64>,
expiry: Option<u64>,
stdin: Option<String>,
wait: bool,
let erstr =
execution_request_file.ok_or(anyhow::anyhow!("Execution request file not provided"))?;
let erfile = File::open(erstr)?;
let execution_request_file: ExecutionRequestFile = serde_json::from_reader(erfile)?;
let inputs = if let Some(inputs) = execution_request_file.inputs {
inputs
} else {
execute_get_inputs(inputs_file, stdin)?
};
let execution_id = execution_id
.or(execution_request_file.execution_id)
.or(Some(rand_id(8)))
.ok_or(anyhow::anyhow!("Execution id not provided"))?;
let image_id = image_id
.or(execution_request_file.image_id)
.ok_or(anyhow::anyhow!("Image id not provided"))?;
let tip = tip
.or(execution_request_file.tip)
.ok_or(anyhow::anyhow!("Tip not provided"))?;
let expiry = expiry
.or(execution_request_file.expiry)
.ok_or(anyhow::anyhow!("Expiry not provided"))?;
let callback_config = execution_request_file.callback_config;
let mut input_hash =
if let Some(input_hash) = execution_request_file.execution_config.input_hash {
hex::decode(&input_hash)
.map_err(|_| anyhow::anyhow!("Invalid input hash, must be hex encoded"))?
vec![]
let signer = keypair.pubkey();
let transformed_inputs = execute_transform_cli_inputs(inputs)?;
let verify_input_hash = execution_request_file
.execution_config
.verify_input_hash
.unwrap_or(false);
let hash_inputs = verify_input_hash
// cannot auto hash private inputs since you need the claim from the prover to get the private inputs
// if requester knows them they can send the hash in the request
&& transformed_inputs.iter().all(|i| i.input_type != InputType::Private);
if hash_inputs {
indicator.set_message("Getting/Hashing inputs");
let rpc_client = Arc::new(RpcClient::new_with_commitment(
rpc_url.clone(),
CommitmentConfig::confirmed(),
let input_resolver =
DefaultInputResolver::new(Arc::new(reqwest::Client::new()), rpc_client);
let hashing_inputs = input_resolver
.resolve_public_inputs(transformed_inputs.clone())
let mut hash = Sha256::new();
for input in hashing_inputs {
if let ProgramInput::Resolved(ri) = input {
hash.update(&ri.data);
return Err(anyhow::anyhow!("Unresolved input"));
input_hash = hash.finalize().to_vec();
let execution_config = ExecutionConfig {
verify_input_hash,
input_hash: Some(&input_hash),
forward_output: execution_request_file
.forward_output
.unwrap_or(false),
let expiry = expiry + current_block;
println!("Execution expiry {}", expiry);
println!("current block {}", current_block);
indicator.set_message("Building transaction");
let ixs = sdk
.execute_v1(
&signer,
&image_id,
&execution_id,
transformed_inputs
.iter()
.map(|i| InputRef::new(i.input_type, i.data.as_deref().unwrap_or_default()))
.collect(),
tip,
expiry,
execution_config,
callback_config.map(|c| c.into()),
None, // A future cli change can implement prover version selection
)
indicator.finish_with_message("Sending transaction");
sdk.send_txn_standard(&keypair, ixs).await?;
indicator.finish_with_message("Waiting for execution");
if wait {
execution_waiter(sdk, keypair.pubkey(), execution_id, expiry, timeout).await?;
Ok(())