mod deployment;
pub use deployment::*;
mod execution;
pub use execution::*;
mod fee;
pub use fee::*;
mod bytes;
mod merkle;
mod serialize;
mod string;
use crate::Transition;
use console::{
network::prelude::*,
program::{Ciphertext, ProgramOwner, Record, TRANSACTION_DEPTH, TransactionLeaf, TransactionPath, TransactionTree},
types::{Field, Group, U64},
};
#[derive(Clone, PartialEq, Eq)]
pub enum Transaction<N: Network> {
Deploy(N::TransactionID, ProgramOwner<N>, Box<Deployment<N>>, Fee<N>),
Execute(N::TransactionID, Execution<N>, Option<Fee<N>>),
Fee(N::TransactionID, Fee<N>),
}
impl<N: Network> Transaction<N> {
pub fn from_deployment(owner: ProgramOwner<N>, deployment: Deployment<N>, fee: Fee<N>) -> Result<Self> {
ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty deployment transaction");
let id = *Self::deployment_tree(&deployment, Some(&fee))?.root();
let deployment_id = deployment.to_deployment_id()?;
ensure!(owner.verify(deployment_id), "Attempted to create a deployment transaction with an invalid owner");
Ok(Self::Deploy(id.into(), owner, Box::new(deployment), fee))
}
pub fn from_execution(execution: Execution<N>, fee: Option<Fee<N>>) -> Result<Self> {
ensure!(!execution.is_empty(), "Attempted to create an empty execution transaction");
let id = *Self::execution_tree(&execution, &fee)?.root();
Ok(Self::Execute(id.into(), execution, fee))
}
pub fn from_fee(fee: Fee<N>) -> Result<Self> {
ensure!(!fee.is_zero()?, "Attempted to create a zero fee transaction");
let id = *Self::fee_tree(&fee)?.root();
Ok(Self::Fee(id.into(), fee))
}
}
impl<N: Network> Transaction<N> {
#[inline]
pub const fn is_deploy(&self) -> bool {
matches!(self, Self::Deploy(..))
}
#[inline]
pub const fn is_execute(&self) -> bool {
matches!(self, Self::Execute(..))
}
#[inline]
pub const fn is_fee(&self) -> bool {
matches!(self, Self::Fee(..))
}
}
impl<N: Network> Transaction<N> {
#[inline]
pub fn contains_split(&self) -> bool {
match self {
Transaction::Execute(_, execution, _) => execution.transitions().any(|transition| transition.is_split()),
_ => false,
}
}
}
impl<N: Network> Transaction<N> {
#[inline]
pub fn owner(&self) -> Option<&ProgramOwner<N>> {
match self {
Self::Deploy(_, owner, _, _) => Some(owner),
_ => None,
}
}
#[inline]
pub fn deployment(&self) -> Option<&Deployment<N>> {
match self {
Self::Deploy(_, _, deployment, _) => Some(deployment.as_ref()),
_ => None,
}
}
#[inline]
pub fn execution(&self) -> Option<&Execution<N>> {
match self {
Self::Execute(_, execution, _) => Some(execution),
_ => None,
}
}
}
enum IterWrap<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> {
Deploy(I1),
Execute(I2),
Fee(I3),
}
impl<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> Iterator for IterWrap<T, I1, I2, I3> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Deploy(iter) => iter.next(),
Self::Execute(iter) => iter.next(),
Self::Fee(iter) => iter.next(),
}
}
}
impl<T, I1: DoubleEndedIterator<Item = T>, I2: DoubleEndedIterator<Item = T>, I3: DoubleEndedIterator<Item = T>>
DoubleEndedIterator for IterWrap<T, I1, I2, I3>
{
fn next_back(&mut self) -> Option<Self::Item> {
match self {
Self::Deploy(iter) => iter.next_back(),
Self::Execute(iter) => iter.next_back(),
Self::Fee(iter) => iter.next_back(),
}
}
}
impl<N: Network> Transaction<N> {
pub const fn id(&self) -> N::TransactionID {
match self {
Self::Deploy(id, ..) => *id,
Self::Execute(id, ..) => *id,
Self::Fee(id, ..) => *id,
}
}
pub fn fee_amount(&self) -> Result<U64<N>> {
match self {
Self::Deploy(_, _, _, fee) => fee.amount(),
Self::Execute(_, _, Some(fee)) => fee.amount(),
Self::Execute(_, _, None) => Ok(U64::zero()),
Self::Fee(_, fee) => fee.amount(),
}
}
pub fn base_fee_amount(&self) -> Result<U64<N>> {
match self {
Self::Deploy(_, _, _, fee) => fee.base_amount(),
Self::Execute(_, _, Some(fee)) => fee.base_amount(),
Self::Execute(_, _, None) => Ok(U64::zero()),
Self::Fee(_, fee) => fee.base_amount(),
}
}
pub fn priority_fee_amount(&self) -> Result<U64<N>> {
match self {
Self::Deploy(_, _, _, fee) => fee.priority_amount(),
Self::Execute(_, _, Some(fee)) => fee.priority_amount(),
Self::Execute(_, _, None) => Ok(U64::zero()),
Self::Fee(_, fee) => fee.priority_amount(),
}
}
pub fn fee_transition(&self) -> Option<Fee<N>> {
match self {
Self::Deploy(_, _, _, fee) => Some(fee.clone()),
Self::Execute(_, _, fee) => fee.clone(),
Self::Fee(_, fee) => Some(fee.clone()),
}
}
}
impl<N: Network> Transaction<N> {
pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool {
match self {
Self::Deploy(_, _, _, fee) => fee.id() == transition_id,
Self::Execute(_, execution, fee) => {
execution.contains_transition(transition_id)
|| fee.as_ref().map_or(false, |fee| fee.id() == transition_id)
}
Self::Fee(_, fee) => fee.id() == transition_id,
}
}
pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
self.transitions().any(|transition| transition.contains_serial_number(serial_number))
}
pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
self.transitions().any(|transition| transition.contains_commitment(commitment))
}
}
impl<N: Network> Transaction<N> {
pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> {
match self {
Self::Deploy(_, _, _, fee) => match fee.id() == transition_id {
true => Some(fee.transition()),
false => None,
},
Self::Execute(_, execution, fee) => execution.get_transition(transition_id).or_else(|| {
fee.as_ref().and_then(|fee| match fee.id() == transition_id {
true => Some(fee.transition()),
false => None,
})
}),
Self::Fee(_, fee) => match fee.id() == transition_id {
true => Some(fee.transition()),
false => None,
},
}
}
pub fn find_transition_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transition<N>> {
self.transitions().find(|transition| transition.contains_serial_number(serial_number))
}
pub fn find_transition_for_commitment(&self, commitment: &Field<N>) -> Option<&Transition<N>> {
self.transitions().find(|transition| transition.contains_commitment(commitment))
}
pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
self.transitions().find_map(|transition| transition.find_record(commitment))
}
}
impl<N: Network> Transaction<N> {
pub fn transition_ids(&self) -> impl '_ + DoubleEndedIterator<Item = &N::TransitionID> {
self.transitions().map(Transition::id)
}
pub fn transitions(&self) -> impl '_ + DoubleEndedIterator<Item = &Transition<N>> {
match self {
Self::Deploy(_, _, _, fee) => IterWrap::Deploy(Some(fee.transition()).into_iter()),
Self::Execute(_, execution, fee) => {
IterWrap::Execute(execution.transitions().chain(fee.as_ref().map(|fee| fee.transition())))
}
Self::Fee(_, fee) => IterWrap::Fee(Some(fee.transition()).into_iter()),
}
}
pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transitions().flat_map(Transition::input_ids)
}
pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transitions().flat_map(Transition::serial_numbers)
}
pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transitions().flat_map(Transition::tags)
}
pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transitions().flat_map(Transition::output_ids)
}
pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transitions().flat_map(Transition::commitments)
}
pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
self.transitions().flat_map(Transition::records)
}
pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
self.transitions().flat_map(Transition::nonces)
}
pub fn transition_public_keys(&self) -> impl '_ + DoubleEndedIterator<Item = &Group<N>> {
self.transitions().map(Transition::tpk)
}
pub fn transition_commitments(&self) -> impl '_ + DoubleEndedIterator<Item = &Field<N>> {
self.transitions().map(Transition::tcm)
}
}
impl<N: Network> Transaction<N> {
pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> {
self.into_transitions().map(Transition::into_id)
}
pub fn into_transitions(self) -> impl DoubleEndedIterator<Item = Transition<N>> {
match self {
Self::Deploy(_, _, _, fee) => IterWrap::Deploy(Some(fee.into_transition()).into_iter()),
Self::Execute(_, execution, fee) => {
IterWrap::Execute(execution.into_transitions().chain(fee.map(|fee| fee.into_transition())))
}
Self::Fee(_, fee) => IterWrap::Fee(Some(fee.into_transition()).into_iter()),
}
}
pub fn into_transition_public_keys(self) -> impl DoubleEndedIterator<Item = Group<N>> {
self.into_transitions().map(Transition::into_tpk)
}
pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
self.into_transitions().flat_map(Transition::into_tags)
}
pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
self.into_transitions().flat_map(Transition::into_serial_numbers)
}
pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
self.into_transitions().flat_map(Transition::into_commitments)
}
pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
self.into_transitions().flat_map(Transition::into_records)
}
pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
self.into_transitions().flat_map(Transition::into_nonces)
}
}
#[cfg(test)]
pub mod test_helpers {
use super::*;
use console::{account::PrivateKey, network::MainnetV0, program::ProgramOwner};
type CurrentNetwork = MainnetV0;
pub fn sample_deployment_transaction(is_fee_private: bool, rng: &mut TestRng) -> Transaction<CurrentNetwork> {
let private_key = PrivateKey::new(rng).unwrap();
let deployment = crate::transaction::deployment::test_helpers::sample_deployment(rng);
let deployment_id = deployment.to_deployment_id().unwrap();
let owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap();
let fee = match is_fee_private {
true => crate::transaction::fee::test_helpers::sample_fee_private(deployment_id, rng),
false => crate::transaction::fee::test_helpers::sample_fee_public(deployment_id, rng),
};
Transaction::from_deployment(owner, deployment, fee).unwrap()
}
pub fn sample_execution_transaction_with_fee(
is_fee_private: bool,
rng: &mut TestRng,
) -> Transaction<CurrentNetwork> {
let execution = crate::transaction::execution::test_helpers::sample_execution(rng);
let execution_id = execution.to_execution_id().unwrap();
let fee = match is_fee_private {
true => crate::transaction::fee::test_helpers::sample_fee_private(execution_id, rng),
false => crate::transaction::fee::test_helpers::sample_fee_public(execution_id, rng),
};
Transaction::from_execution(execution, Some(fee)).unwrap()
}
pub fn sample_private_fee_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
let fee = crate::transaction::fee::test_helpers::sample_fee_private_hardcoded(rng);
Transaction::from_fee(fee).unwrap()
}
pub fn sample_fee_public_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
let fee = crate::transaction::fee::test_helpers::sample_fee_public_hardcoded(rng);
Transaction::from_fee(fee).unwrap()
}
}