use anyhow::{Context, Result};
use indexmap::IndexMap;
use snarkvm::algorithms::snark::varuna::VarunaVersion;
use snarkvm::circuit::AleoTestnetV0;
use snarkvm::console::program::{Identifier, ProgramID, ProgramOwner};
use snarkvm::ledger::block::{Deployment, Transaction};
use snarkvm::prelude::{
Address, ConsensusVersion, CryptoRng, InclusionVersion, PrivateKey, Process, Program, Response,
Rng, TestRng, TestnetV0,
};
use snarkvm::synthesizer::process::{Stack, Trace, deployment_cost};
use std::str::FromStr;
use std::sync::Arc;
pub use snarkvm::ledger::query::QueryTrait;
mod fee_keys {
use snarkvm::parameters::testnet::{FeePublicV0Prover, FeePublicV0Verifier};
use snarkvm::prelude::FromBytes as _;
use snarkvm::synthesizer::snark::{ProvingKey, VerifyingKey};
pub fn load_pk() -> Result<ProvingKey<snarkvm::prelude::TestnetV0>, anyhow::Error> {
ProvingKey::from_bytes_le(&FeePublicV0Prover::load_bytes()?)
}
pub fn load_vk() -> Result<VerifyingKey<snarkvm::prelude::TestnetV0>, anyhow::Error> {
VerifyingKey::from_bytes_le(&FeePublicV0Verifier::load_bytes()?)
}
}
pub struct ExecutionEngine {
pub process: Process<TestnetV0>,
}
impl ExecutionEngine {
pub fn new() -> Result<Self> {
let process = Process::<TestnetV0>::load()?;
Ok(Self { process })
}
pub fn new_with_v0_fee_keys() -> Result<Self> {
let mut engine = Self::new()?;
engine.inject_v0_fee_keys()?;
Ok(engine)
}
pub fn inject_v0_fee_keys(&mut self) -> Result<()> {
tracing::info!("Loading V0 fee keys from testnet parameters...");
let fee_pk = fee_keys::load_pk().context("Failed to deserialize V0 fee proving key")?;
let fee_vk = fee_keys::load_vk().context("Failed to deserialize V0 fee verifying key")?;
let credits_id = ProgramID::<TestnetV0>::from_str("credits.aleo")?;
let fee_fn = Identifier::<TestnetV0>::from_str("fee_public")?;
let guard = self.process.lock();
guard.insert_proving_key(&credits_id, &fee_fn, fee_pk)?;
guard.insert_verifying_key(&credits_id, &fee_fn, fee_vk)?;
drop(guard);
tracing::info!("V0 fee keys injected");
Ok(())
}
pub fn add_program(&self, program: &Program<TestnetV0>) -> Result<()> {
self.process.lock().add_program(program)?;
Ok(())
}
pub fn authorize_and_execute(
&self,
private_key: &PrivateKey<TestnetV0>,
program_id: &ProgramID<TestnetV0>,
function_name: &str,
inputs: Vec<&str>,
rng: &mut TestRng,
) -> Result<(Response<TestnetV0>, Trace<TestnetV0>)> {
tracing::info!("Authorizing...");
let fn_id = Identifier::<TestnetV0>::from_str(function_name)?;
let authorization = self
.process
.authorize::<AleoTestnetV0, _>(private_key, *program_id, fn_id, inputs.into_iter(), rng)
.context("Authorization failed")?;
tracing::info!("Executing locally...");
let (response, trace) = self
.process
.execute::<AleoTestnetV0, _>(authorization, rng)
.context("Local execution failed")?;
Ok((response, trace))
}
#[allow(clippy::too_many_arguments)]
pub fn prove_and_package(
&self,
trace: Trace<TestnetV0>,
private_key: &PrivateKey<TestnetV0>,
program_id: &ProgramID<TestnetV0>,
function_name: &str,
base_fee: u64,
priority_fee: u64,
query: &impl QueryTrait<TestnetV0>,
rng: &mut TestRng,
) -> Result<Transaction<TestnetV0>> {
let locator = format!("{program_id}/{function_name}");
let mut exec_trace = trace;
exec_trace.prepare(query).context("Failed to prepare execution trace")?;
let execution = exec_trace
.prove_execution::<AleoTestnetV0, _>(&locator, VarunaVersion::V2, rng)
.context("Failed to generate execution proof")?;
let execution_id = execution.to_execution_id()?;
let fee_authorization = self
.process
.authorize_fee_public::<AleoTestnetV0, _>(
private_key,
base_fee,
priority_fee,
execution_id,
rng,
)
.context("Failed to authorize fee")?;
let (_fee_response, mut fee_trace) = self
.process
.execute::<AleoTestnetV0, _>(fee_authorization, rng)
.context("Failed to execute fee")?;
fee_trace.prepare(query).context("Failed to prepare fee trace")?;
let fee = fee_trace
.prove_fee::<AleoTestnetV0, _>(VarunaVersion::V2, rng)
.context("Failed to generate fee proof")?;
let mut execution_stacks: IndexMap<ProgramID<TestnetV0>, Arc<Stack<TestnetV0>>> =
IndexMap::new();
let guard = self.process.lock();
for transition in execution.transitions() {
let pid = *transition.program_id();
if !execution_stacks.contains_key(&pid) {
let stack = guard.get_stack(pid).context("Missing stack for verification")?;
execution_stacks.insert(pid, stack.clone());
}
}
drop(guard);
tracing::info!("Verifying proofs locally...");
Process::<TestnetV0>::verify_execution(
ConsensusVersion::V14,
VarunaVersion::V2,
InclusionVersion::V0,
&execution,
&execution_stacks,
)
.context("Local execution verification FAILED")?;
self.process
.verify_fee(
ConsensusVersion::V14,
VarunaVersion::V2,
InclusionVersion::V0,
&fee,
execution_id,
)
.context("Local fee verification FAILED")?;
Transaction::<TestnetV0>::from_execution(execution, Some(fee))
.context("Failed to package transaction")
}
pub fn inner(&self) -> &Process<TestnetV0> {
&self.process
}
pub fn deploy_program<R: Rng + CryptoRng>(
&self,
program: &Program<TestnetV0>,
rng: &mut R,
) -> Result<Deployment<TestnetV0>> {
self.process
.deploy::<AleoTestnetV0, R>(program, rng)
.context("Failed to generate program deployment")
}
#[allow(clippy::too_many_arguments)]
pub fn build_deployment_transaction<R: Rng + CryptoRng>(
&self,
private_key: &PrivateKey<TestnetV0>,
_program: &Program<TestnetV0>,
deployment: &Deployment<TestnetV0>,
base_fee: u64,
priority_fee_in_microcredits: u64,
_consensus_version: ConsensusVersion,
query: &impl QueryTrait<TestnetV0>,
rng: &mut R,
) -> Result<Transaction<TestnetV0>> {
if deployment.program().functions().is_empty() {
anyhow::bail!("Attempted to create an empty deployment");
}
let mut deployment = deployment.clone();
deployment.set_edition_raw(0);
let addr = Address::try_from(private_key)?;
deployment.set_program_owner_raw(Some(addr));
deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
let deployment_id = deployment.to_deployment_id()?;
let owner = ProgramOwner::new(private_key, deployment_id, rng)?;
let fee_authorization = self
.process
.authorize_fee_public::<AleoTestnetV0, R>(
private_key,
base_fee,
priority_fee_in_microcredits,
deployment_id,
rng,
)
.context("Failed to authorize deployment fee")?;
let (_, mut fee_trace) = self
.process
.execute::<AleoTestnetV0, R>(fee_authorization, rng)
.context("Failed to execute fee")?;
fee_trace.prepare(query).context("Failed to prepare fee trace")?;
let fee = fee_trace
.prove_fee::<AleoTestnetV0, R>(VarunaVersion::V2, rng)
.context("Failed to prove fee")?;
Transaction::<TestnetV0>::from_deployment(owner, deployment.clone(), fee)
.context("Failed to package deployment transaction")
}
pub fn deployment_cost_minimum(
&self,
deployment: &Deployment<TestnetV0>,
_consensus_version: ConsensusVersion,
) -> Result<u64> {
let (min_cost, _details) = deployment_cost(&self.process, deployment, _consensus_version)?;
Ok(min_cost)
}
pub fn verify_execution_transaction(
&self,
execution: &snarkvm::ledger::block::Execution<TestnetV0>,
) -> Result<()> {
let mut execution_stacks: IndexMap<ProgramID<TestnetV0>, Arc<Stack<TestnetV0>>> =
IndexMap::new();
let guard = self.process.lock();
for t in execution.transitions() {
let pid = *t.program_id();
if !execution_stacks.contains_key(&pid) {
let stack = guard.get_stack(pid).context("Missing stack for verification")?;
execution_stacks.insert(pid, stack.clone());
}
}
drop(guard);
Process::<TestnetV0>::verify_execution(
ConsensusVersion::V14,
VarunaVersion::V2,
InclusionVersion::V0,
execution,
&execution_stacks,
)
.context("Execution proof verification FAILED")
}
pub fn verify_deployment_transaction(
&self,
deployment: &Deployment<TestnetV0>,
rng: &mut TestRng,
) -> Result<()> {
self.process
.verify_deployment::<AleoTestnetV0, _>(ConsensusVersion::V14, deployment, rng)
.context("Deployment proof verification FAILED")
}
}