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,
DeploymentTree,
ExecutionTree,
ProgramOwner,
Record,
TRANSACTION_DEPTH,
TransactionLeaf,
TransactionPath,
TransactionTree,
},
types::{Field, Group, U64},
};
type DeploymentID<N> = Field<N>;
type ExecutionID<N> = Field<N>;
#[derive(Clone, PartialEq, Eq)]
pub enum Transaction<N: Network> {
Deploy(N::TransactionID, DeploymentID<N>, ProgramOwner<N>, Box<Deployment<N>>, Fee<N>),
Execute(N::TransactionID, ExecutionID<N>, Box<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 deployment_tree = Self::deployment_tree(&deployment)?;
let deployment_id = *deployment_tree.root();
let transaction_id = *Self::transaction_tree(deployment_tree, Some(&fee))?.root();
ensure!(owner.verify(deployment_id), "Attempted to create a deployment transaction with an invalid owner");
if let Some(program_owner) = deployment.program_owner() {
ensure!(
owner.address() == program_owner,
"Attempted to create a deployment transaction with a provided owner '{}' and deployment owner '{}' that do not match",
owner.address(),
program_owner
)
}
Ok(Self::Deploy(transaction_id.into(), deployment_id, 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 execution_tree = Self::execution_tree(&execution)?;
let execution_id = *execution_tree.root();
let transaction_id = *Self::transaction_tree(execution_tree, fee.as_ref())?.root();
Ok(Self::Execute(transaction_id.into(), execution_id, Box::new(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,
}
}
#[inline]
pub fn contains_upgrade(&self) -> bool {
match self {
Transaction::Execute(_, _, execution, _) => {
execution.transitions().any(|transition| transition.is_upgrade())
}
_ => 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().is_some_and(|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, types::Address};
type CurrentNetwork = MainnetV0;
pub fn sample_deployment_transaction(
version: u8,
edition: u16,
has_translation_keys: bool,
is_fee_private: bool,
rng: &mut TestRng,
) -> Transaction<CurrentNetwork> {
let private_key = PrivateKey::new(rng).unwrap();
let deployment = match (version, has_translation_keys) {
(1, false) => crate::transaction::deployment::test_helpers::sample_deployment_v1(edition, rng),
(2, false) => {
let mut deployment =
crate::transaction::deployment::test_helpers::sample_deployment_v2_without_translation_keys(
edition, rng,
);
deployment.set_program_owner_raw(Some(Address::try_from(&private_key).unwrap()));
deployment
}
(2, true) => {
let mut deployment =
crate::transaction::deployment::test_helpers::sample_deployment_v2_with_translation_keys(
edition, rng,
);
deployment.set_program_owner_raw(Some(Address::try_from(&private_key).unwrap()));
deployment
}
(3, _) => {
crate::transaction::deployment::test_helpers::sample_deployment_v3(edition, rng)
}
_ => panic!("Invalid deployment version ({version}) or translation keys combination."),
};
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,
index: usize,
) -> Transaction<CurrentNetwork> {
let execution = crate::transaction::execution::test_helpers::sample_execution(rng, index);
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()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_id() -> Result<()> {
let rng = &mut TestRng::default();
for expected in [
crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, true, rng),
crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, true, rng),
crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, false, rng),
crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, false, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, true, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, true, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, false, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, false, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, true, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, true, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, false, rng),
crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, false, rng),
crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0),
crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0),
]
.into_iter()
{
match expected {
Transaction::Deploy(transaction_id, deployment_id, _, ref deployment, _) => {
let expected_transaction_id = *expected.clone().to_tree()?.root();
assert_eq!(expected_transaction_id, *transaction_id);
let expected_deployment_id = *Transaction::deployment_tree(deployment)?.root();
assert_eq!(expected_deployment_id, deployment_id);
}
Transaction::Execute(transaction_id, execution_id, ref execution, _) => {
let expected_transaction_id = *expected.clone().to_tree()?.root();
assert_eq!(expected_transaction_id, *transaction_id);
let expected_execution_id = *Transaction::execution_tree(execution)?.root();
assert_eq!(expected_execution_id, execution_id);
}
_ => panic!("Unexpected test case."),
};
}
Ok(())
}
}