1
use crate::common::*;
2
use anyhow::Result;
3
use bonsol_prover::input_resolver::{DefaultInputResolver, InputResolver, ProgramInput};
4
use bonsol_sdk::instructions::{ExecutionConfig, InputRef};
5
use bonsol_sdk::{BonsolClient, ExecutionAccountStatus, InputType};
6
use indicatif::ProgressBar;
7
use sha2::{Digest, Sha256};
8
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
9
use solana_sdk::bs58;
10
use solana_sdk::commitment_config::CommitmentConfig;
11
use solana_sdk::pubkey::Pubkey;
12
use solana_sdk::signer::Signer;
13
use std::fs::File;
14
use std::sync::Arc;
15
use tokio::time::Instant;
16

            
17
pub async fn execution_waiter(
18
    sdk: &BonsolClient,
19
    requester: Pubkey,
20
    execution_id: String,
21
    expiry: u64,
22
    timeout: Option<u64>,
23
) -> Result<()> {
24
    let indicator = ProgressBar::new_spinner();
25

            
26
    let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(1));
27
    let now = Instant::now();
28
    loop {
29
        if let Some(timeout) = timeout {
30
            if now.elapsed().as_secs() > timeout {
31
                return Err(anyhow::anyhow!("Timeout"));
32
            }
33
        }
34
        interval.tick().await;
35

            
36
        let current_block = sdk.get_current_slot().await?;
37
        indicator.set_message(format!(
38
            "Waiting for execution to be claimed, current block {} expiry {}",
39
            current_block, expiry
40
        ));
41
        if current_block > expiry {
42
            indicator.finish_with_message("Execution expired");
43
            return Err(anyhow::anyhow!("Execution expired"));
44
        }
45

            
46
        let claim_state = sdk.get_claim_state_v1(&requester, &execution_id).await;
47
        if let Ok(claim_state) = claim_state {
48
            let claim = claim_state.claim()?;
49
            indicator.finish_with_message(format!(
50
                "Claimed by {} at slot {}, committed {}",
51
                bs58::encode(claim.claimer).into_string(),
52
                claim.claimed_at,
53
                claim.block_commitment
54
            ));
55
            break;
56
        }
57
    }
58
    //now we are looking for execution request finished
59
    loop {
60
        if let Some(timeout) = timeout {
61
            if now.elapsed().as_secs() > timeout {
62
                indicator.finish_with_message("Execution timed out");
63
                return Err(anyhow::anyhow!("Timeout"));
64
            }
65
        }
66
        interval.tick().await;
67
        let exec_status = sdk
68
            .get_execution_request_v1(&requester, &execution_id)
69
            .await?;
70
        match exec_status {
71
            ExecutionAccountStatus::Completed(ec) => {
72
                indicator.finish_with_message(format!("Execution completed with exit code {}", ec));
73
                return Ok(());
74
            }
75
            ExecutionAccountStatus::Pending(_) => {
76
                indicator.tick();
77
                continue;
78
            }
79
        }
80
    }
81
}
82

            
83
pub async fn execute(
84
    sdk: &BonsolClient,
85
    rpc_url: String,
86
    keypair: impl Signer,
87
    execution_request_file: Option<String>,
88
    image_id: Option<String>,
89
    execution_id: Option<String>,
90
    timeout: Option<u64>,
91
    inputs_file: Option<String>,
92
    tip: Option<u64>,
93
    expiry: Option<u64>,
94
    stdin: Option<String>,
95
    wait: bool,
96
) -> Result<()> {
97
    let indicator = ProgressBar::new_spinner();
98
    let erstr =
99
        execution_request_file.ok_or(anyhow::anyhow!("Execution request file not provided"))?;
100
    let erfile = File::open(erstr)?;
101
    let execution_request_file: ExecutionRequestFile = serde_json::from_reader(erfile)?;
102
    let inputs = if let Some(inputs) = execution_request_file.inputs {
103
        inputs
104
    } else {
105
        execute_get_inputs(inputs_file, stdin)?
106
    };
107
    let execution_id = execution_id
108
        .or(execution_request_file.execution_id)
109
        .or(Some(rand_id(8)))
110
        .ok_or(anyhow::anyhow!("Execution id not provided"))?;
111
    let image_id = image_id
112
        .or(execution_request_file.image_id)
113
        .ok_or(anyhow::anyhow!("Image id not provided"))?;
114
    let tip = tip
115
        .or(execution_request_file.tip)
116
        .ok_or(anyhow::anyhow!("Tip not provided"))?;
117
    let expiry = expiry
118
        .or(execution_request_file.expiry)
119
        .ok_or(anyhow::anyhow!("Expiry not provided"))?;
120
    let callback_config = execution_request_file.callback_config;
121
    let mut input_hash =
122
        if let Some(input_hash) = execution_request_file.execution_config.input_hash {
123
            hex::decode(&input_hash)
124
                .map_err(|_| anyhow::anyhow!("Invalid input hash, must be hex encoded"))?
125
        } else {
126
            vec![]
127
        };
128

            
129
    let signer = keypair.pubkey();
130
    let transformed_inputs = execute_transform_cli_inputs(inputs)?;
131
    let verify_input_hash = execution_request_file
132
        .execution_config
133
        .verify_input_hash
134
        .unwrap_or(false);
135
    let hash_inputs = verify_input_hash
136
        // cannot auto hash private inputs since you need the claim from the prover to get the private inputs
137
        // if requester knows them they can send the hash in the request
138
        && transformed_inputs.iter().all(|i| i.input_type != InputType::Private);
139
    if hash_inputs {
140
        indicator.set_message("Getting/Hashing inputs");
141
        let rpc_client = Arc::new(RpcClient::new_with_commitment(
142
            rpc_url.clone(),
143
            CommitmentConfig::confirmed(),
144
        ));
145
        let input_resolver =
146
            DefaultInputResolver::new(Arc::new(reqwest::Client::new()), rpc_client);
147
        let hashing_inputs = input_resolver
148
            .resolve_public_inputs(transformed_inputs.clone())
149
            .await?;
150
        let mut hash = Sha256::new();
151
        for input in hashing_inputs {
152
            if let ProgramInput::Resolved(ri) = input {
153
                hash.update(&ri.data);
154
            } else {
155
                return Err(anyhow::anyhow!("Unresolved input"));
156
            }
157
        }
158
        input_hash = hash.finalize().to_vec();
159
    }
160
    let execution_config = ExecutionConfig {
161
        verify_input_hash,
162
        input_hash: Some(&input_hash),
163
        forward_output: execution_request_file
164
            .execution_config
165
            .forward_output
166
            .unwrap_or(false),
167
    };
168
    let current_block = sdk.get_current_slot().await?;
169
    let expiry = expiry + current_block;
170
    println!("Execution expiry {}", expiry);
171
    println!("current block {}", current_block);
172
    indicator.set_message("Building transaction");
173
    let ixs = sdk
174
        .execute_v1(
175
            &signer,
176
            &image_id,
177
            &execution_id,
178
            transformed_inputs
179
                .iter()
180
                .map(|i| InputRef::new(i.input_type, i.data.as_deref().unwrap_or_default()))
181
                .collect(),
182
            tip,
183
            expiry,
184
            execution_config,
185
            callback_config.map(|c| c.into()),
186
            None, // A future cli change can implement prover version selection
187
        )
188
        .await?;
189
    indicator.finish_with_message("Sending transaction");
190
    sdk.send_txn_standard(&keypair, ixs).await?;
191
    indicator.finish_with_message("Waiting for execution");
192
    if wait {
193
        execution_waiter(sdk, keypair.pubkey(), execution_id, expiry, timeout).await?;
194
    }
195
    Ok(())
196
}