1use anyhow::{Context, Result};
19use indexmap::IndexMap;
20use snarkvm::algorithms::snark::varuna::VarunaVersion;
21use snarkvm::circuit::AleoTestnetV0;
22use snarkvm::console::program::{Identifier, ProgramID, ProgramOwner, Value};
23use snarkvm::ledger::block::{Deployment, Transaction};
24use snarkvm::prelude::{
25 Address, ConsensusVersion, CryptoRng, InclusionVersion, PrivateKey, Process, Program, Response,
26 Rng, TestRng, TestnetV0,
27};
28use snarkvm::synthesizer::process::{Stack, Trace, deployment_cost};
29use std::str::FromStr;
30use std::sync::Arc;
31
32pub use snarkvm::ledger::query::QueryTrait;
34
35mod fee_keys {
37 use snarkvm::parameters::testnet::{FeePublicV0Prover, FeePublicV0Verifier};
38 use snarkvm::prelude::FromBytes as _;
39 use snarkvm::synthesizer::snark::{ProvingKey, VerifyingKey};
40
41 pub fn load_pk() -> Result<ProvingKey<snarkvm::prelude::TestnetV0>, anyhow::Error> {
42 ProvingKey::from_bytes_le(&FeePublicV0Prover::load_bytes()?)
43 }
44
45 pub fn load_vk() -> Result<VerifyingKey<snarkvm::prelude::TestnetV0>, anyhow::Error> {
46 VerifyingKey::from_bytes_le(&FeePublicV0Verifier::load_bytes()?)
47 }
48}
49
50pub struct ExecutionEngine {
52 pub process: Process<TestnetV0>,
53}
54
55impl ExecutionEngine {
56 pub fn new() -> Result<Self> {
58 let process = Process::<TestnetV0>::load()?;
59 Ok(Self { process })
60 }
61
62 pub fn new_with_v0_fee_keys() -> Result<Self> {
64 let mut engine = Self::new()?;
65 engine.inject_v0_fee_keys()?;
66 Ok(engine)
67 }
68
69 pub fn inject_v0_fee_keys(&mut self) -> Result<()> {
71 tracing::info!("Loading V0 fee keys from testnet parameters...");
72 let fee_pk = fee_keys::load_pk().context("Failed to deserialize V0 fee proving key")?;
73 let fee_vk = fee_keys::load_vk().context("Failed to deserialize V0 fee verifying key")?;
74
75 let credits_id = ProgramID::<TestnetV0>::from_str("credits.aleo")?;
76 let fee_fn = Identifier::<TestnetV0>::from_str("fee_public")?;
77 let guard = self.process.lock();
78 guard.insert_proving_key(&credits_id, &fee_fn, fee_pk)?;
79 guard.insert_verifying_key(&credits_id, &fee_fn, fee_vk)?;
80 drop(guard);
81 tracing::info!("V0 fee keys injected");
82 Ok(())
83 }
84
85 pub fn add_program(&self, program: &Program<TestnetV0>) -> Result<()> {
87 self.process.lock().add_program(program)?;
89 Ok(())
90 }
91
92 pub fn authorize_and_execute_with_values(
95 &self,
96 private_key: &PrivateKey<TestnetV0>,
97 program_id: &ProgramID<TestnetV0>,
98 function_name: &str,
99 values: Vec<Value<TestnetV0>>,
100 rng: &mut TestRng,
101 ) -> Result<(Response<TestnetV0>, Trace<TestnetV0>)> {
102 tracing::info!("Authorizing with pre-parsed values...");
103 let fn_id = Identifier::<TestnetV0>::from_str(function_name)?;
104 let authorization = self
105 .process
106 .authorize::<AleoTestnetV0, _>(private_key, *program_id, fn_id, values.into_iter(), rng)
107 .context("Authorization failed")?;
108
109 tracing::info!("Executing locally...");
110 let (response, trace) = self
111 .process
112 .execute::<AleoTestnetV0, _>(authorization, rng)
113 .context("Local execution failed")?;
114
115 tracing::info!("Execution succeeded");
116 Ok((response, trace))
117 }
118
119 pub fn authorize_and_execute(
124 &self,
125 private_key: &PrivateKey<TestnetV0>,
126 program_id: &ProgramID<TestnetV0>,
127 function_name: &str,
128 inputs: Vec<&str>,
129 rng: &mut TestRng,
130 ) -> Result<(Response<TestnetV0>, Trace<TestnetV0>)> {
131 tracing::info!("Authorizing...");
132 let fn_id = Identifier::<TestnetV0>::from_str(function_name)?;
133 let authorization = self
134 .process
135 .authorize::<AleoTestnetV0, _>(private_key, *program_id, fn_id, inputs.into_iter(), rng)
136 .context("Authorization failed")?;
137
138 tracing::info!("Executing locally...");
139 let (response, trace) = self
140 .process
141 .execute::<AleoTestnetV0, _>(authorization, rng)
142 .context("Local execution failed")?;
143
144 Ok((response, trace))
145 }
146
147 #[allow(clippy::too_many_arguments)]
149 pub fn prove_and_package(
150 &self,
151 trace: Trace<TestnetV0>,
152 private_key: &PrivateKey<TestnetV0>,
153 program_id: &ProgramID<TestnetV0>,
154 function_name: &str,
155 base_fee: u64,
156 priority_fee: u64,
157 query: &impl QueryTrait<TestnetV0>,
158 rng: &mut TestRng,
159 ) -> Result<Transaction<TestnetV0>> {
160 let locator = format!("{program_id}/{function_name}");
161
162 let mut exec_trace = trace;
164 exec_trace.prepare(query).context("Failed to prepare execution trace")?;
165 let execution = exec_trace
166 .prove_execution::<AleoTestnetV0, _>(&locator, VarunaVersion::V2, rng)
167 .context("Failed to generate execution proof")?;
168
169 let execution_id = execution.to_execution_id()?;
171 let fee_authorization = self
172 .process
173 .authorize_fee_public::<AleoTestnetV0, _>(
174 private_key,
175 base_fee,
176 priority_fee,
177 execution_id,
178 rng,
179 )
180 .context("Failed to authorize fee")?;
181
182 let (_fee_response, mut fee_trace) = self
183 .process
184 .execute::<AleoTestnetV0, _>(fee_authorization, rng)
185 .context("Failed to execute fee")?;
186
187 fee_trace.prepare(query).context("Failed to prepare fee trace")?;
188 let fee = fee_trace
189 .prove_fee::<AleoTestnetV0, _>(VarunaVersion::V2, rng)
190 .context("Failed to generate fee proof")?;
191
192 let mut execution_stacks: IndexMap<ProgramID<TestnetV0>, Arc<Stack<TestnetV0>>> =
194 IndexMap::new();
195 let guard = self.process.lock();
196 for transition in execution.transitions() {
197 let pid = *transition.program_id();
198 if !execution_stacks.contains_key(&pid) {
199 let stack = guard.get_stack(pid).context("Missing stack for verification")?;
200 execution_stacks.insert(pid, stack.clone());
201 }
202 }
203 drop(guard);
204
205 tracing::info!("Verifying proofs locally...");
207 Process::<TestnetV0>::verify_execution(
208 ConsensusVersion::V14,
209 VarunaVersion::V2,
210 InclusionVersion::V1,
211 &execution,
212 &execution_stacks,
213 )
214 .context("Local execution verification FAILED")?;
215
216 self.process
217 .verify_fee(
218 ConsensusVersion::V14,
219 VarunaVersion::V2,
220 InclusionVersion::V1,
221 &fee,
222 execution_id,
223 )
224 .context("Local fee verification FAILED")?;
225
226 Transaction::<TestnetV0>::from_execution(execution, Some(fee))
227 .context("Failed to package transaction")
228 }
229
230 pub fn inner(&self) -> &Process<TestnetV0> {
232 &self.process
233 }
234
235 pub fn deploy_program<R: Rng + CryptoRng>(
242 &self,
243 program: &Program<TestnetV0>,
244 rng: &mut R,
245 ) -> Result<Deployment<TestnetV0>> {
246 self.process
247 .deploy::<AleoTestnetV0, R>(program, rng)
248 .context("Failed to generate program deployment")
249 }
250
251 #[allow(clippy::too_many_arguments)]
261 pub fn build_deployment_transaction<R: Rng + CryptoRng>(
262 &self,
263 private_key: &PrivateKey<TestnetV0>,
264 _program: &Program<TestnetV0>,
265 deployment: &Deployment<TestnetV0>,
266 base_fee: u64,
267 priority_fee_in_microcredits: u64,
268 _consensus_version: ConsensusVersion,
269 query: &impl QueryTrait<TestnetV0>,
270 rng: &mut R,
271 ) -> Result<Transaction<TestnetV0>> {
272 if deployment.program().functions().is_empty() {
274 anyhow::bail!("Attempted to create an empty deployment");
275 }
276
277 let mut deployment = deployment.clone();
282 deployment.set_edition_raw(0);
283 let addr = Address::try_from(private_key)?;
284 deployment.set_program_owner_raw(Some(addr));
285 deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
286
287 let deployment_id = deployment.to_deployment_id()?;
288 let owner = ProgramOwner::new(private_key, deployment_id, rng)?;
289
290 let fee_authorization = self
292 .process
293 .authorize_fee_public::<AleoTestnetV0, R>(
294 private_key,
295 base_fee,
296 priority_fee_in_microcredits,
297 deployment_id,
298 rng,
299 )
300 .context("Failed to authorize deployment fee")?;
301
302 let (_, mut fee_trace) = self
304 .process
305 .execute::<AleoTestnetV0, R>(fee_authorization, rng)
306 .context("Failed to execute fee")?;
307
308 fee_trace.prepare(query).context("Failed to prepare fee trace")?;
309
310 let fee = fee_trace
311 .prove_fee::<AleoTestnetV0, R>(VarunaVersion::V2, rng)
312 .context("Failed to prove fee")?;
313
314 Transaction::<TestnetV0>::from_deployment(owner, deployment.clone(), fee)
316 .context("Failed to package deployment transaction")
317 }
318
319 pub fn deployment_cost_minimum(
321 &self,
322 deployment: &Deployment<TestnetV0>,
323 _consensus_version: ConsensusVersion,
324 ) -> Result<u64> {
325 let (min_cost, _details) = deployment_cost(&self.process, deployment, _consensus_version)?;
326 Ok(min_cost)
327 }
328
329 pub fn verify_execution_transaction(
335 &self,
336 execution: &snarkvm::ledger::block::Execution<TestnetV0>,
337 ) -> Result<()> {
338 let mut execution_stacks: IndexMap<ProgramID<TestnetV0>, Arc<Stack<TestnetV0>>> =
340 IndexMap::new();
341 let guard = self.process.lock();
342 for t in execution.transitions() {
343 let pid = *t.program_id();
344 if !execution_stacks.contains_key(&pid) {
345 let stack = guard.get_stack(pid).context("Missing stack for verification")?;
346 execution_stacks.insert(pid, stack.clone());
347 }
348 }
349 drop(guard);
350
351 Process::<TestnetV0>::verify_execution(
352 ConsensusVersion::V14,
353 VarunaVersion::V2,
354 InclusionVersion::V1,
355 execution,
356 &execution_stacks,
357 )
358 .context("Execution proof verification FAILED")
359 }
360
361 pub fn verify_deployment_transaction(
364 &self,
365 deployment: &Deployment<TestnetV0>,
366 rng: &mut TestRng,
367 ) -> Result<()> {
368 self.process
369 .verify_deployment::<AleoTestnetV0, _>(ConsensusVersion::V14, deployment, rng)
370 .context("Deployment proof verification FAILED")
371 }
372}