1
use bonsol_schema::{
2
    Account, ChannelInstruction, ChannelInstructionArgs, ChannelInstructionIxType, DeployV1,
3
    DeployV1Args, ExecutionRequestV1, ExecutionRequestV1Args, InputBuilder, InputType,
4
    ProgramInputType, ProverVersion,
5
};
6
use flatbuffers::{FlatBufferBuilder, WIPOffset};
7

            
8
use crate::error::ClientError;
9
use crate::util::{deployment_address, execution_address};
10

            
11
#[cfg(feature = "on-chain")]
12
use {
13
    solana_program::instruction::AccountMeta, solana_program::instruction::Instruction,
14
    solana_program::pubkey::Pubkey, solana_program::system_program,
15
};
16

            
17
#[cfg(not(feature = "on-chain"))]
18
use {
19
    solana_sdk::instruction::AccountMeta, solana_sdk::instruction::Instruction,
20
    solana_sdk::pubkey::Pubkey, solana_sdk::system_program,
21
};
22

            
23
pub fn deploy_v1(
24
    signer: &Pubkey,
25
    image_id: &str,
26
    image_size: u64,
27
    program_name: &str,
28
    url: &str,
29
    inputs: Vec<ProgramInputType>,
30
) -> Result<Instruction, ClientError> {
31
    let (deployment_account, _) = deployment_address(image_id);
32
    let accounts = vec![
33
        AccountMeta::new(signer.to_owned(), true),
34
        AccountMeta::new(signer.to_owned(), true),
35
        AccountMeta::new(deployment_account, false),
36
        AccountMeta::new_readonly(system_program::id(), false),
37
    ];
38
    let mut fbb = FlatBufferBuilder::new();
39
    let url = fbb.create_string(url);
40
    let image_id = fbb.create_string(image_id);
41
    let name = fbb.create_string(program_name);
42
    let owner = fbb.create_vector(signer.as_ref());
43
    let fb_inputs = fbb.create_vector(inputs.as_slice());
44
    let fbb_deploy = DeployV1::create(
45
        &mut fbb,
46
        &DeployV1Args {
47
            owner: Some(owner),
48
            image_id: Some(image_id),
49
            program_name: Some(name),
50
            url: Some(url),
51
            size_: image_size,
52
            inputs: Some(fb_inputs),
53
        },
54
    );
55
    fbb.finish(fbb_deploy, None);
56
    let ix_data = fbb.finished_data();
57
    let mut fbb = FlatBufferBuilder::new();
58
    let ix = fbb.create_vector(ix_data);
59
    let fbb_ix = ChannelInstruction::create(
60
        &mut fbb,
61
        &ChannelInstructionArgs {
62
            ix_type: ChannelInstructionIxType::DeployV1,
63
            deploy_v1: Some(ix),
64
            ..Default::default()
65
        },
66
    );
67
    fbb.finish(fbb_ix, None);
68
    let ix_data = fbb.finished_data();
69
    Ok(Instruction::new_with_bytes(crate::ID, ix_data, accounts))
70
}
71

            
72
// todo hold attributes for scheme and versions selection
73
#[derive(Debug, Clone)]
74
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
76
pub struct ExecutionConfig<'a> {
77
    pub verify_input_hash: bool,
78
    pub input_hash: Option<&'a [u8]>,
79
    pub forward_output: bool,
80
}
81

            
82
#[cfg(feature = "serde")]
83
pub mod serde_helpers {
84
    pub mod pubkey {
85
        use std::str::FromStr;
86

            
87
        use serde::{self, Deserialize, Deserializer, Serializer};
88
        use solana_sdk::pubkey::Pubkey;
89

            
90
        pub fn serialize<S>(value: &Pubkey, serializer: S) -> Result<S::Ok, S::Error>
91
        where
92
            S: Serializer,
93
        {
94
            serializer.serialize_str(&value.to_string())
95
        }
96

            
97
        pub fn deserialize<'de, D>(deserializer: D) -> Result<Pubkey, D::Error>
98
        where
99
            D: Deserializer<'de>,
100
        {
101
            let s = String::deserialize(deserializer)?;
102
            Pubkey::from_str(&s).map_err(serde::de::Error::custom)
103
        }
104
    }
105

            
106
    pub mod optpubkey {
107
        use std::str::FromStr;
108

            
109
        use serde::{self, Deserialize, Deserializer, Serializer};
110
        use solana_sdk::pubkey::Pubkey;
111

            
112
        pub fn serialize<S>(value: &Option<Pubkey>, serializer: S) -> Result<S::Ok, S::Error>
113
        where
114
            S: Serializer,
115
        {
116
            match value {
117
                Some(v) => serializer.serialize_str(&v.to_string()),
118
                None => serializer.serialize_none(),
119
            }
120
        }
121

            
122
        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Pubkey>, D::Error>
123
        where
124
            D: Deserializer<'de>,
125
        {
126
            let s = String::deserialize(deserializer)?;
127
            Pubkey::from_str(&s)
128
                .map_err(serde::de::Error::custom)
129
                .map(Some)
130
        }
131
    }
132
}
133

            
134
impl<'a> ExecutionConfig<'a> {
135
    pub fn validate(&self) -> Result<(), ClientError> {
136
        if self.verify_input_hash && self.input_hash.is_none() {
137
            return Err(ClientError::InvalidInput);
138
        }
139
        Ok(())
140
    }
141
}
142

            
143
impl Default for ExecutionConfig<'_> {
144
    fn default() -> Self {
145
        ExecutionConfig {
146
            verify_input_hash: true,
147
            input_hash: None,
148
            forward_output: false,
149
        }
150
    }
151
}
152

            
153
#[derive(Debug, Clone)]
154
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
156
pub struct CallbackConfig {
157
    #[cfg_attr(feature = "serde", serde(default, with = "serde_helpers::pubkey"))]
158
    pub program_id: Pubkey,
159
    pub instruction_prefix: Vec<u8>,
160
    pub extra_accounts: Vec<AccountMeta>,
161
}
162

            
163
pub struct InputRef<'a> {
164
    pub input_type: InputType,
165
    pub data: &'a [u8],
166
}
167

            
168
impl<'a> InputRef<'a> {
169
    pub fn new(input_type: InputType, data: &'a [u8]) -> Self {
170
        Self { input_type, data }
171
    }
172

            
173
    pub fn public(data: &'a [u8]) -> Self {
174
        Self {
175
            input_type: InputType::PublicData,
176
            data,
177
        }
178
    }
179
    pub fn private(data: &'a [u8]) -> Self {
180
        Self {
181
            input_type: InputType::Private,
182
            data,
183
        }
184
    }
185
    pub fn public_proof(data: &'a [u8]) -> Self {
186
        Self {
187
            input_type: InputType::PublicProof,
188
            data,
189
        }
190
    }
191
    pub fn url(data: &'a [u8]) -> Self {
192
        Self {
193
            input_type: InputType::PublicUrl,
194
            data,
195
        }
196
    }
197
    pub fn public_account(data: &'a [u8]) -> Self {
198
        Self {
199
            input_type: InputType::PublicAccountData,
200
            data,
201
        }
202
    }
203
}
204

            
205
/// Executes a bonsol program.
206
/// This sends and instruction to the bonsol program which requests execution from the bonsol network
207
pub fn execute_v1<'a>(
208
    requester: &Pubkey,
209
    payer: &Pubkey,
210
    image_id: &str,
211
    execution_id: &str,
212
    inputs: Vec<InputRef<'a>>,
213
    tip: u64,
214
    expiration: u64,
215
    config: ExecutionConfig<'a>,
216
    callback: Option<CallbackConfig>,
217
    prover_version: Option<ProverVersion>,
218
) -> Result<Instruction, ClientError> {
219
    let (execution_account, _) = execution_address(requester, execution_id.as_bytes());
220
    let (deployment_account, _) = deployment_address(image_id);
221
    execute_v1_with_accounts(
222
        requester,
223
        payer,
224
        &execution_account,
225
        &deployment_account,
226
        image_id,
227
        execution_id,
228
        inputs,
229
        tip,
230
        expiration,
231
        config,
232
        callback,
233
        prover_version,
234
    )
235
}
236
/// Executes a bonsol program with the provided accounts
237
/// This is more efficient than using the execute_v1 function
238
/// but requires the user to provide the accounts
239
pub fn execute_v1_with_accounts<'a>(
240
    requester: &Pubkey,
241
    payer: &Pubkey,
242
    execution_account: &Pubkey,
243
    deployment_account: &Pubkey,
244
    image_id: &str,
245
    execution_id: &str,
246
    inputs: Vec<InputRef>,
247
    tip: u64,
248
    expiration: u64,
249
    config: ExecutionConfig,
250
    callback: Option<CallbackConfig>,
251
    prover_version: Option<ProverVersion>,
252
) -> Result<Instruction, ClientError> {
253
    config.validate()?;
254
    let mut fbb = FlatBufferBuilder::new();
255
    let mut callback_pubkey = None; // avoid clone
256
    let (callback_program_id, callback_instruction_prefix, extra_accounts) =
257
        if let Some(cb) = callback {
258
            callback_pubkey = Some(cb.program_id);
259
            let cb_program_id = fbb.create_vector(cb.program_id.as_ref());
260
            let cb_instruction_prefix = fbb.create_vector(cb.instruction_prefix.as_slice());
261
            let ealen = cb.extra_accounts.len();
262
            fbb.start_vector::<WIPOffset<Account>>(ealen);
263
            for ea in cb.extra_accounts.iter().rev() {
264
                let pkbytes = arrayref::array_ref!(ea.pubkey.as_ref(), 0, 32);
265
                let eab = Account::new(ea.is_writable as u8, pkbytes);
266
                fbb.push(eab);
267
            }
268
            (
269
                Some(cb_program_id),
270
                Some(cb_instruction_prefix),
271
                Some(fbb.end_vector(ealen)),
272
            )
273
        } else {
274
            (None, None, None)
275
        };
276
    let accounts = vec![
277
        AccountMeta::new(*requester, true),
278
        AccountMeta::new(*payer, true),
279
        AccountMeta::new(*execution_account, false),
280
        AccountMeta::new_readonly(*deployment_account, false),
281
        AccountMeta::new_readonly(callback_pubkey.unwrap_or(crate::ID), false),
282
        AccountMeta::new_readonly(system_program::id(), false),
283
    ];
284
    let inputlen = inputs.len();
285
    let mut inputs_vec = Vec::with_capacity(inputlen);
286
    for input in inputs {
287
        let data_off = fbb.create_vector(input.data);
288
        let mut ibb = InputBuilder::new(&mut fbb);
289
        ibb.add_data(data_off);
290
        ibb.add_input_type(input.input_type);
291
        let input = ibb.finish();
292
        inputs_vec.push(input);
293
    }
294
    let fb_inputs = fbb.create_vector(&inputs_vec);
295
    let image_id = fbb.create_string(image_id);
296
    let execution_id = fbb.create_string(execution_id);
297

            
298
    let input_digest = config.input_hash.map(|ih| fbb.create_vector(ih));
299

            
300
    // typically cli will pass None for the optional prover_version indicating bonsol should handle
301
    // the default case here
302
    let prover_version = prover_version.unwrap_or_default();
303
    let fbb_execute = ExecutionRequestV1::create(
304
        &mut fbb,
305
        &ExecutionRequestV1Args {
306
            tip,
307
            execution_id: Some(execution_id),
308
            image_id: Some(image_id),
309
            callback_program_id,
310
            callback_instruction_prefix,
311
            forward_output: config.forward_output,
312
            verify_input_hash: config.verify_input_hash,
313
            input: Some(fb_inputs),
314
            max_block_height: expiration,
315
            input_digest,
316
            callback_extra_accounts: extra_accounts,
317
            prover_version,
318
        },
319
    );
320
    fbb.finish(fbb_execute, None);
321
    let ix_data = fbb.finished_data();
322
    let mut fbb = FlatBufferBuilder::new();
323
    let ix = fbb.create_vector(ix_data);
324
    let fbb_ix = ChannelInstruction::create(
325
        &mut fbb,
326
        &ChannelInstructionArgs {
327
            ix_type: ChannelInstructionIxType::ExecuteV1,
328
            execute_v1: Some(ix),
329
            ..Default::default()
330
        },
331
    );
332
    fbb.finish(fbb_ix, None);
333
    let ix_data = fbb.finished_data();
334
    Ok(Instruction::new_with_bytes(crate::ID, ix_data, accounts))
335
}