snarkvm_synthesizer_process/stack/authorization/
mod.rsmod bytes;
mod serialize;
mod string;
use console::{network::prelude::*, program::Request, types::Field};
use ledger_block::{Transaction, Transition};
use indexmap::IndexMap;
use parking_lot::RwLock;
use std::{collections::VecDeque, sync::Arc};
#[derive(Clone)]
pub struct Authorization<N: Network> {
requests: Arc<RwLock<VecDeque<Request<N>>>>,
transitions: Arc<RwLock<IndexMap<N::TransitionID, Transition<N>>>>,
}
impl<N: Network> Authorization<N> {
pub fn new(request: Request<N>) -> Self {
Self { requests: Arc::new(RwLock::new(VecDeque::from(vec![request]))), transitions: Default::default() }
}
pub fn replicate(&self) -> Self {
Self {
requests: Arc::new(RwLock::new(self.requests.read().clone())),
transitions: Arc::new(RwLock::new(self.transitions.read().clone())),
}
}
}
impl<N: Network> TryFrom<(Vec<Request<N>>, Vec<Transition<N>>)> for Authorization<N> {
type Error = Error;
fn try_from((requests, transitions): (Vec<Request<N>>, Vec<Transition<N>>)) -> Result<Self> {
ensure!(
requests.len() == transitions.len(),
"The number of requests ({}) and transitions ({}) must match in the authorization.",
requests.len(),
transitions.len()
);
for (index, (request, transition)) in requests.iter().zip_eq(&transitions).enumerate() {
ensure_request_and_transition_matches(index, request, transition)?;
}
Ok(Self {
requests: Arc::new(RwLock::new(VecDeque::from(requests))),
transitions: Arc::new(RwLock::new(IndexMap::from_iter(
transitions.into_iter().map(|transition| (*transition.id(), transition)),
))),
})
}
}
impl<N: Network> Authorization<N> {
pub fn is_fee_private(&self) -> bool {
let requests = self.requests.read();
match requests.len() {
1 => {
let program_id = requests[0].program_id().to_string();
let function_name = requests[0].function_name().to_string();
&program_id == "credits.aleo" && &function_name == "fee_private"
}
_ => false,
}
}
pub fn is_fee_public(&self) -> bool {
let requests = self.requests.read();
match requests.len() {
1 => {
let program_id = requests[0].program_id().to_string();
let function_name = requests[0].function_name().to_string();
&program_id == "credits.aleo" && &function_name == "fee_public"
}
_ => false,
}
}
pub fn is_split(&self) -> bool {
let requests = self.requests.read();
match requests.len() {
1 => {
let program_id = requests[0].program_id().to_string();
let function_name = requests[0].function_name().to_string();
&program_id == "credits.aleo" && &function_name == "split"
}
_ => false,
}
}
}
impl<N: Network> Authorization<N> {
pub fn peek_next(&self) -> Result<Request<N>> {
self.requests.read().front().cloned().ok_or_else(|| anyhow!("Failed to peek at the next request."))
}
pub fn next(&self) -> Result<Request<N>> {
self.requests.write().pop_front().ok_or_else(|| anyhow!("No more requests in the authorization."))
}
pub fn get(&self, index: usize) -> Result<Request<N>> {
self.requests.read().get(index).cloned().ok_or_else(|| anyhow!("Attempted to get missing request {index}."))
}
pub fn len(&self) -> usize {
self.requests.read().len()
}
pub fn is_empty(&self) -> bool {
self.requests.read().is_empty()
}
pub fn push(&self, request: Request<N>) {
self.requests.write().push_back(request);
}
pub fn to_vec_deque(&self) -> VecDeque<Request<N>> {
self.requests.read().clone()
}
}
impl<N: Network> Authorization<N> {
pub fn insert_transition(&self, transition: Transition<N>) -> Result<()> {
ensure!(
!self.transitions.read().contains_key(transition.id()),
"Transition {} is already in the authorization.",
transition.id()
);
self.transitions.write().insert(*transition.id(), transition);
Ok(())
}
pub fn transitions(&self) -> IndexMap<N::TransitionID, Transition<N>> {
self.transitions.read().clone()
}
pub fn to_execution_id(&self) -> Result<Field<N>> {
let transitions = self.transitions.read();
if transitions.is_empty() {
bail!("Cannot compute the execution ID for an empty authorization.");
}
Ok(*Transaction::transitions_tree(transitions.values(), &None)?.root())
}
}
impl<N: Network> PartialEq for Authorization<N> {
fn eq(&self, other: &Self) -> bool {
let self_requests = self.requests.read();
let other_requests = other.requests.read();
let self_transitions = self.transitions.read();
let other_transitions = other.transitions.read();
*self_requests == *other_requests && *self_transitions == *other_transitions
}
}
impl<N: Network> Eq for Authorization<N> {}
fn ensure_request_and_transition_matches<N: Network>(
index: usize,
request: &Request<N>,
transition: &Transition<N>,
) -> Result<()> {
ensure!(
request.program_id() == transition.program_id(),
"The request ({}) and transition ({}) at index {index} must have the same program ID in the authorization.",
request.program_id(),
transition.program_id(),
);
ensure!(
request.function_name() == transition.function_name(),
"The request ({}) and transition ({}) at index {index} must have the same function name in the authorization.",
request.function_name(),
transition.function_name(),
);
ensure!(
request.input_ids().len() == transition.input_ids().len(),
"The request ({}) and transition ({}) at index {index} must have the same number of inputs in the authorization.",
request.input_ids().len(),
transition.input_ids().len(),
);
ensure!(
request.to_tpk() == *transition.tpk(),
"The request ({}) and transition ({}) at index {index} must have the same 'tpk' in the authorization.",
request.to_tpk(),
*transition.tpk(),
);
ensure!(
request.tcm() == transition.tcm(),
"The request ({}) and transition ({}) at index {index} must have the same 'tcm' in the authorization.",
request.tcm(),
transition.tcm(),
);
ensure!(
request.scm() == transition.scm(),
"The request ({}) and transition ({}) at index {index} must have the same 'scm' in the authorization.",
request.scm(),
transition.scm(),
);
Ok(())
}
#[cfg(test)]
pub(crate) mod test_helpers {
use super::*;
use crate::Process;
use console::account::PrivateKey;
type CurrentNetwork = console::network::MainnetV0;
type CurrentAleo = circuit::AleoV0;
pub fn sample_authorization(rng: &mut TestRng) -> Authorization<CurrentNetwork> {
let process = Process::<CurrentNetwork>::load().unwrap();
let private_key = PrivateKey::new(rng).unwrap();
let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2);
let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2);
let deployment_or_execution_id = Field::rand(rng);
let authorization = process
.authorize_fee_public::<CurrentAleo, _>(
&private_key,
base_fee_in_microcredits,
priority_fee_in_microcredits,
deployment_or_execution_id,
rng,
)
.unwrap();
assert!(authorization.is_fee_public(), "Authorization must be for a call to 'credits.aleo/fee_public'");
authorization
}
}