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, InputT, InputType};
use hex;
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;
}
}
loop {
if let Some(timeout) = timeout {
if now.elapsed().as_secs() > timeout {
indicator.finish_with_message("Execution timed out");
return Err(anyhow::anyhow!("Timeout"));
}
}
interval.tick().await;
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(
sdk: &BonsolClient,
rpc_url: String,
keypair: impl Signer,
execution_request_file: Option<String>,
image_id: Option<String>,
execution_id: Option<String>,
timeout: Option<u64>,
inputs_file: Option<String>,
tip: Option<u64>,
expiry: Option<u64>,
stdin: Option<String>,
wait: bool,
) -> Result<()> {
let indicator = ProgressBar::new_spinner();
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"))?
} else {
vec![]
};
let signer = keypair.pubkey();
let transformed_cli_inputs = execute_transform_cli_inputs(inputs)?;
let single_concatenated_input_data: Option<Vec<u8>> = if transformed_cli_inputs
.iter()
.all(|i| i.input_type == InputType::PublicData)
&& !transformed_cli_inputs.is_empty()
{
let mut concatenated_bytes: Vec<u8> = Vec::new();
for input_t_val in &transformed_cli_inputs {
if let Some(bytes) = &input_t_val.data {
concatenated_bytes.extend_from_slice(bytes);
}
}
if !concatenated_bytes.is_empty() {
Some(concatenated_bytes)
} else {
None }
} else {
None
};
let final_input_refs: Vec<InputRef> =
if let Some(ref data_bytes) = single_concatenated_input_data {
if data_bytes.is_empty() && !transformed_cli_inputs.is_empty() {
vec![InputRef::new(InputType::PublicData, data_bytes.as_slice())]
} else if !data_bytes.is_empty() {
vec![InputRef::new(InputType::PublicData, data_bytes.as_slice())]
} else {
vec![]
}
} else {
transformed_cli_inputs
.iter()
.map(|input_t_val| {
InputRef::new(
input_t_val.input_type,
input_t_val.data.as_deref().unwrap_or_default(),
)
})
.collect()
};
let verify_input_hash = execution_request_file
.execution_config
.verify_input_hash
.unwrap_or(false);
let hash_inputs = verify_input_hash
&& transformed_cli_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 resolvable_inputs_for_hashing: Vec<InputT> = transformed_cli_inputs
.iter()
.map(
|i_t|
i_t.clone(), )
.collect();
let hashing_inputs = input_resolver
.resolve_public_inputs(resolvable_inputs_for_hashing)
.await?;
let mut hash_obj = Sha256::new(); for prog_input in hashing_inputs {
if let ProgramInput::Resolved(ri) = prog_input {
hash_obj.update(&ri.data);
} else {
return Err(anyhow::anyhow!("Unresolved input during hashing"));
}
}
input_hash = hash_obj.finalize().to_vec();
}
let execution_config = ExecutionConfig {
verify_input_hash: execution_request_file
.execution_config
.verify_input_hash
.unwrap_or(false),
input_hash: Some(&input_hash),
forward_output: execution_request_file
.execution_config
.forward_output
.unwrap_or(false),
};
let current_block = sdk.get_current_slot().await?;
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,
final_input_refs,
tip,
expiry,
execution_config,
callback_config.map(|c| c.into()),
None, vec![],
)
.await?;
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(())
}