From a99a40fb4e7e2f52cc1617f7d93f2038b173e131 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:43:32 -0800 Subject: [PATCH 01/46] Introduce V2 variant of program owner --- console/program/src/owner/bytes.rs | 74 ++++-- console/program/src/owner/mod.rs | 220 ++++++++++++++++-- console/program/src/owner/serialize.rs | 87 +++++-- ledger/block/src/transaction/mod.rs | 2 +- ledger/block/src/transactions/rejected/mod.rs | 2 +- ledger/test-helpers/src/lib.rs | 4 +- synthesizer/src/vm/deploy.rs | 2 +- 7 files changed, 332 insertions(+), 59 deletions(-) diff --git a/console/program/src/owner/bytes.rs b/console/program/src/owner/bytes.rs index 0328ed9cc8..10a558a537 100644 --- a/console/program/src/owner/bytes.rs +++ b/console/program/src/owner/bytes.rs @@ -21,29 +21,57 @@ impl FromBytes for ProgramOwner { // Read the version. let version = u8::read_le(&mut reader)?; // Ensure the version is valid. - if version != 1 { - return Err(error("Invalid program owner version")); - } + match version { + 1 => { + // Read the address. + let address = Address::read_le(&mut reader)?; + // Read the signature. + let signature = Signature::read_le(&mut reader)?; - // Read the address. - let address = Address::read_le(&mut reader)?; - // Read the signature. - let signature = Signature::read_le(&mut reader)?; + // Return the program owner. + Ok(Self::V1(ProgramOwnerV1::from(address, signature))) + } + 2 => { + // Read the address. + let address = Address::read_le(&mut reader)?; + // Read the authority. + let authority = Address::read_le(&mut reader)?; + // Read the signature. + let signature = Signature::read_le(&mut reader)?; - // Return the program owner. - Ok(Self::from(address, signature)) + // Return the program owner. + Ok(Self::V2(ProgramOwnerV2::from(address, authority, signature))) + } + _ => { + return Err(error("Invalid program owner version")); + } + } } } impl ToBytes for ProgramOwner { /// Writes the program owner to a buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { - // Write the version. - 1u8.write_le(&mut writer)?; - // Write the address. - self.address.write_le(&mut writer)?; - // Write the signature. - self.signature.write_le(&mut writer) + match &self { + Self::V1(owner) => { + // Write the version. + 1u8.write_le(&mut writer)?; + // Write the address. + owner.address.write_le(&mut writer)?; + // Write the signature. + owner.signature.write_le(&mut writer) + } + Self::V2(owner) => { + // Write the version. + 2u8.write_le(&mut writer)?; + // Write the address. + owner.address.write_le(&mut writer)?; + // Write the authority. + owner.authority.write_le(&mut writer)?; + // Write the signature. + owner.signature.write_le(&mut writer) + } + } } } @@ -55,9 +83,21 @@ mod tests { type CurrentNetwork = MainnetV0; #[test] - fn test_bytes() -> Result<()> { + fn test_bytes_v1() -> Result<()> { + // Construct a new program owner. + let expected = test_helpers::sample_program_owner_v1(); + + // Check the byte representation. + let expected_bytes = expected.to_bytes_le()?; + assert_eq!(expected, ProgramOwner::read_le(&expected_bytes[..])?); + assert!(ProgramOwner::::read_le(&expected_bytes[1..]).is_err()); + Ok(()) + } + + #[test] + fn test_bytes_v2() -> Result<()> { // Construct a new program owner. - let expected = test_helpers::sample_program_owner(); + let expected = test_helpers::sample_program_owner_v2(); // Check the byte representation. let expected_bytes = expected.to_bytes_le()?; diff --git a/console/program/src/owner/mod.rs b/console/program/src/owner/mod.rs index 6a3fe985e0..e33d80e5b5 100644 --- a/console/program/src/owner/mod.rs +++ b/console/program/src/owner/mod.rs @@ -21,36 +21,127 @@ use snarkvm_console_account::{Address, PrivateKey, Signature}; use snarkvm_console_network::Network; use snarkvm_console_types::prelude::*; +/// Metadata regarding an owner of a program. #[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct ProgramOwner { +pub enum ProgramOwner { + /// A V1 program owner. + V1(ProgramOwnerV1), + /// A V2 program owner. + V2(ProgramOwnerV2), +} + +impl ProgramOwner { + /// Initializes a new V1 program owner. + pub fn new_v1( + private_key: &PrivateKey, + deployment_id: Field, + rng: &mut R, + ) -> Result { + Ok(Self::V1(ProgramOwnerV1::new(private_key, deployment_id, rng)?)) + } + + /// Initializes a new V2 program owner. + pub fn new_v2( + private_key: &PrivateKey, + authority: Address, + deployment_id: Field, + rng: &mut R, + ) -> Result { + Ok(Self::V2(ProgramOwnerV2::new(private_key, authority, deployment_id, rng)?)) + } + + /// Returns the program owner as a V1 owner. + pub fn as_v1(&self) -> Option<&ProgramOwnerV1> { + match self { + Self::V1(owner) => Some(owner), + _ => None, + } + } + + /// Returns the program owner as a V2 owner. + pub fn as_v2(&self) -> Option<&ProgramOwnerV2> { + match self { + Self::V2(owner) => Some(owner), + _ => None, + } + } + + /// Returns whether the program owner is a V1 owner. + pub const fn is_v1(&self) -> bool { + matches!(self, Self::V1(_)) + } + + /// Returns whether the program owner is a V2 owner. + pub const fn is_v2(&self) -> bool { + matches!(self, Self::V2(_)) + } +} + +impl ProgramOwner { + /// Returns the address of the program owner. + pub const fn address(&self) -> &Address { + match self { + Self::V1(owner) => owner.address(), + Self::V2(owner) => owner.address(), + } + } + + /// Returns the authority of the program owner. + pub const fn authority(&self) -> Option<&Address> { + match self { + Self::V1(_) => None, + Self::V2(owner) => Some(&owner.authority()), + } + } + + /// Returns the signature of the program owner. + pub const fn signature(&self) -> &Signature { + match self { + Self::V1(owner) => owner.signature(), + Self::V2(owner) => owner.signature(), + } + } + + /// Verify that the signature is valid for the given deployment ID. + pub fn verify(&self, deployment_id: Field) -> bool { + match self { + Self::V1(owner) => owner.verify(deployment_id), + Self::V2(owner) => owner.verify(deployment_id), + } + } +} + +/// A V1 program owner. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct ProgramOwnerV1 { /// The address of the program owner. address: Address, /// The signature of the program owner, over the deployment transaction ID. signature: Signature, } -impl ProgramOwner { - /// Initializes a new program owner. +impl ProgramOwnerV1 { + /// Initializes a new V1 program owner. pub fn new(private_key: &PrivateKey, deployment_id: Field, rng: &mut R) -> Result { // Derive the address. let address = Address::try_from(private_key)?; // Sign the transaction ID. let signature = private_key.sign(&[deployment_id], rng)?; - // Return the program owner. - Ok(Self { signature, address }) + // Return the V2 program owner. + Ok(Self { address, signature }) } - /// Initializes a new program owner from an address and signature. + /// Initializes a new V1 program owner from an address and signature. pub fn from(address: Address, signature: Signature) -> Self { Self { address, signature } } - /// Returns the address of the program owner. - pub const fn address(&self) -> Address { - self.address + /// Returns the address of the V1 program owner. + pub const fn address(&self) -> &Address { + &self.address } - /// Returns the signature of the program owner. + /// Returns the signature of the V1 program owner. pub const fn signature(&self) -> &Signature { &self.signature } @@ -61,6 +152,59 @@ impl ProgramOwner { } } +/// A V2 program owner. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct ProgramOwnerV2 { + /// The address of the program owner. + address: Address, + /// The address of the authority allowed to update the program. + authority: Address, + /// The signature of the program owner, over the deployment transaction ID. + signature: Signature, +} + +impl ProgramOwnerV2 { + /// Initializes a new V2 program owner. + pub fn new( + private_key: &PrivateKey, + authority: Address, + deployment_id: Field, + rng: &mut R, + ) -> Result { + // Derive the address. + let address = Address::try_from(private_key)?; + // Sign the transaction ID. + let signature = private_key.sign(&[authority.to_x_coordinate(), deployment_id], rng)?; + // Return the program owner. + Ok(Self { address, authority, signature }) + } + + /// Initializes a new V2 program owner from an address, authority, and signature. + pub fn from(address: Address, authority: Address, signature: Signature) -> Self { + Self { address, authority, signature } + } + + /// Returns the address of the V2 program owner. + pub const fn address(&self) -> &Address { + &self.address + } + + /// Returns the authority of the V2 program owner. + pub const fn authority(&self) -> &Address { + &self.authority + } + + /// Returns the signature of the V2 program owner. + pub const fn signature(&self) -> &Signature { + &self.signature + } + + /// Verify that the signature is valid for the given deployment ID. + pub fn verify(&self, deployment_id: Field) -> bool { + self.signature.verify(&self.address, &[self.authority.to_x_coordinate(), deployment_id]) + } +} + #[cfg(test)] pub(crate) mod test_helpers { use super::*; @@ -70,9 +214,9 @@ pub(crate) mod test_helpers { type CurrentNetwork = MainnetV0; - pub(crate) fn sample_program_owner() -> ProgramOwner { - static INSTANCE: OnceCell> = OnceCell::new(); - *INSTANCE.get_or_init(|| { + pub(crate) fn sample_program_owner_v1() -> ProgramOwner { + static V1_INSTANCE: OnceCell> = OnceCell::new(); + *V1_INSTANCE.get_or_init(|| { // Initialize the RNG. let rng = &mut TestRng::default(); @@ -83,23 +227,67 @@ pub(crate) mod test_helpers { let deployment_id: Field = rng.gen(); // Return the program owner. - ProgramOwner::new(&private_key, deployment_id, rng).unwrap() + ProgramOwner::new_v1(&private_key, deployment_id, rng).unwrap() }) } + pub(crate) fn sample_program_owner_v2() -> ProgramOwner { + static V2_INSTANCE: OnceCell> = OnceCell::new(); + *V2_INSTANCE.get_or_init(|| { + // Initialize the RNG. + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = PrivateKey::::new(rng).unwrap(); + + // Initialize an authority. + let authority = Address::::try_from(&private_key).unwrap(); + + // Initialize a deployment ID. + let deployment_id: Field = rng.gen(); + + // Return the program owner. + ProgramOwner::new_v2(&private_key, authority, deployment_id, rng).unwrap() + }) + } + + #[test] + fn test_verify_program_owner_v1() { + // Initialize the RNG. + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = PrivateKey::::new(rng).unwrap(); + + // Initialize a deployment ID. + let deployment_id: Field = rng.gen(); + + // Construct the program owner. + let owner = ProgramOwner::new_v1(&private_key, deployment_id, rng).unwrap(); + // Ensure that the program owner is verified for the given deployment ID. + assert!(owner.verify(deployment_id)); + + // Ensure that the program owner is not verified for a different deployment ID. + let incorrect_deployment_id: Field = rng.gen(); + assert!(!owner.verify(incorrect_deployment_id)); + } + #[test] - fn test_verify_program_owner() { + fn test_verify_program_owner_v2() { // Initialize the RNG. let rng = &mut TestRng::default(); // Initialize a private key. let private_key = PrivateKey::::new(rng).unwrap(); + // Initialize an authority. + let authority = Address::::try_from(&private_key).unwrap(); + // Initialize a deployment ID. let deployment_id: Field = rng.gen(); // Construct the program owner. - let owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap(); + let owner = ProgramOwner::new_v2(&private_key, authority, deployment_id, rng).unwrap(); // Ensure that the program owner is verified for the given deployment ID. assert!(owner.verify(deployment_id)); diff --git a/console/program/src/owner/serialize.rs b/console/program/src/owner/serialize.rs index 3b9cf8ddb5..d41c7a7549 100644 --- a/console/program/src/owner/serialize.rs +++ b/console/program/src/owner/serialize.rs @@ -17,16 +17,26 @@ use super::*; use snarkvm_utilities::DeserializeExt; +// TODO (@d0cd) This could use cleanup. impl Serialize for ProgramOwner { /// Serializes the program owner into string or bytes. fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { - true => { - let mut owner = serializer.serialize_struct("ProgramOwner", 2)?; - owner.serialize_field("address", &self.address)?; - owner.serialize_field("signature", &self.signature)?; - owner.end() - } + true => match &self { + Self::V1(program_owner_v1) => { + let mut owner = serializer.serialize_struct("ProgramOwnerV1", 2)?; + owner.serialize_field("address", &program_owner_v1.address())?; + owner.serialize_field("signature", &program_owner_v1.signature())?; + owner.end() + } + Self::V2(program_owner_v2) => { + let mut owner = serializer.serialize_struct("ProgramOwnerV2", 3)?; + owner.serialize_field("address", &program_owner_v2.address())?; + owner.serialize_field("authority", &program_owner_v2.authority())?; + owner.serialize_field("signature", &program_owner_v2.signature())?; + owner.end() + } + }, false => ToBytesSerializer::serialize_with_size_encoding(self, serializer), } } @@ -37,18 +47,19 @@ impl<'de, N: Network> Deserialize<'de> for ProgramOwner { fn deserialize>(deserializer: D) -> Result { match deserializer.is_human_readable() { true => { - // Parse the program owner from a string into a value. let mut owner = serde_json::Value::deserialize(deserializer)?; - - // Recover the program owner. - let owner = Self::from( - // Retrieve the address. - DeserializeExt::take_from_value::(&mut owner, "address")?, - // Retrieve the signature. - DeserializeExt::take_from_value::(&mut owner, "signature")?, - ); - - Ok(owner) + if owner.get("authority").is_some() { + // If the `authority` field is present, then use the V2 format. + let address = DeserializeExt::take_from_value::(&mut owner, "address")?; + let authority = DeserializeExt::take_from_value::(&mut owner, "authority")?; + let signature = DeserializeExt::take_from_value::(&mut owner, "signature")?; + Ok(Self::V2(ProgramOwnerV2::from(address, authority, signature))) + } else { + // Otherwise, use the V1 format. + let address = DeserializeExt::take_from_value::(&mut owner, "address")?; + let signature = DeserializeExt::take_from_value::(&mut owner, "signature")?; + Ok(Self::V1(ProgramOwnerV1::from(address, signature))) + } } false => FromBytesDeserializer::::deserialize_with_size_encoding(deserializer, "program owner"), } @@ -60,9 +71,26 @@ mod tests { use super::*; #[test] - fn test_serde_json() -> Result<()> { + fn test_serde_json_v1() -> Result<()> { + // Sample the program owner. + let expected = test_helpers::sample_program_owner_v1(); + + // Serialize + let expected_string = &expected.to_string(); + let candidate_string = serde_json::to_string(&expected)?; + assert_eq!(expected, serde_json::from_str(&candidate_string)?); + + // Deserialize + assert_eq!(expected, ProgramOwner::from_str(expected_string)?); + assert_eq!(expected, serde_json::from_str(&candidate_string)?); + + Ok(()) + } + + #[test] + fn test_serde_json_v2() -> Result<()> { // Sample the program owner. - let expected = test_helpers::sample_program_owner(); + let expected = test_helpers::sample_program_owner_v2(); // Serialize let expected_string = &expected.to_string(); @@ -77,9 +105,26 @@ mod tests { } #[test] - fn test_bincode() -> Result<()> { + fn test_bincode_v1() -> Result<()> { + // Sample the program owner. + let expected = test_helpers::sample_program_owner_v1(); + + // Serialize + let expected_bytes = expected.to_bytes_le()?; + let expected_bytes_with_size_encoding = bincode::serialize(&expected)?; + assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); + + // Deserialize + assert_eq!(expected, ProgramOwner::read_le(&expected_bytes[..])?); + assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..])?); + + Ok(()) + } + + #[test] + fn test_bincode_v2() -> Result<()> { // Sample the program owner. - let expected = test_helpers::sample_program_owner(); + let expected = test_helpers::sample_program_owner_v2(); // Serialize let expected_bytes = expected.to_bytes_le()?; diff --git a/ledger/block/src/transaction/mod.rs b/ledger/block/src/transaction/mod.rs index 61e49e6afe..d300523145 100644 --- a/ledger/block/src/transaction/mod.rs +++ b/ledger/block/src/transaction/mod.rs @@ -422,7 +422,7 @@ pub mod test_helpers { // Compute the deployment ID. let deployment_id = deployment.to_deployment_id().unwrap(); // Construct a program owner. - let owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap(); + let owner = ProgramOwner::new_v1(&private_key, deployment_id, rng).unwrap(); // Sample the fee. let fee = match is_fee_private { diff --git a/ledger/block/src/transactions/rejected/mod.rs b/ledger/block/src/transactions/rejected/mod.rs index 655ee40f67..a16f169111 100644 --- a/ledger/block/src/transactions/rejected/mod.rs +++ b/ledger/block/src/transactions/rejected/mod.rs @@ -110,7 +110,7 @@ pub mod test_helpers { // Sample a new program owner. let private_key = PrivateKey::new(rng).unwrap(); let deployment_id = deployment.to_deployment_id().unwrap(); - let program_owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap(); + let program_owner = ProgramOwner::new_v1(&private_key, deployment_id, rng).unwrap(); // Return the rejected deployment. Rejected::new_deployment(program_owner, deployment) diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index 7d789c6fdd..1ae367b19a 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -171,7 +171,7 @@ pub fn sample_rejected_deployment(is_fee_private: bool, rng: &mut TestRng) -> Re // Sample a new program owner. let private_key = PrivateKey::new(rng).unwrap(); let deployment_id = deployment.to_deployment_id().unwrap(); - let program_owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap(); + let program_owner = ProgramOwner::new_v1(&private_key, deployment_id, rng).unwrap(); // Return the rejected deployment. Rejected::new_deployment(program_owner, deployment) @@ -360,7 +360,7 @@ pub fn sample_deployment_transaction(is_fee_private: bool, rng: &mut TestRng) -> // Compute the deployment ID. let deployment_id = deployment.to_deployment_id().unwrap(); // Construct a program owner. - let owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap(); + let owner = ProgramOwner::new_v1(&private_key, deployment_id, rng).unwrap(); // Sample the fee. let fee = match is_fee_private { diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index e8104291ea..be69ad2187 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -38,7 +38,7 @@ impl> VM { // Compute the deployment ID. let deployment_id = deployment.to_deployment_id()?; // Construct the owner. - let owner = ProgramOwner::new(private_key, deployment_id, rng)?; + let owner = ProgramOwner::new_v1(private_key, deployment_id, rng)?; // Compute the minimum deployment cost. let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; From d65e63650399998200800a5784c7c83e4a98c4da Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:45:03 -0800 Subject: [PATCH 02/46] Clippy --- console/program/src/owner/bytes.rs | 4 +--- console/program/src/owner/mod.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/console/program/src/owner/bytes.rs b/console/program/src/owner/bytes.rs index 10a558a537..35bcf04883 100644 --- a/console/program/src/owner/bytes.rs +++ b/console/program/src/owner/bytes.rs @@ -42,9 +42,7 @@ impl FromBytes for ProgramOwner { // Return the program owner. Ok(Self::V2(ProgramOwnerV2::from(address, authority, signature))) } - _ => { - return Err(error("Invalid program owner version")); - } + _ => Err(error("Invalid program owner version")), } } } diff --git a/console/program/src/owner/mod.rs b/console/program/src/owner/mod.rs index e33d80e5b5..a5b204f524 100644 --- a/console/program/src/owner/mod.rs +++ b/console/program/src/owner/mod.rs @@ -90,7 +90,7 @@ impl ProgramOwner { pub const fn authority(&self) -> Option<&Address> { match self { Self::V1(_) => None, - Self::V2(owner) => Some(&owner.authority()), + Self::V2(owner) => Some(owner.authority()), } } From d2044f2b1c2f9b449b64353aaab32e5f76885447 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:05:43 -0800 Subject: [PATCH 03/46] Add deploy_with_authority --- ledger/store/src/transaction/deployment.rs | 1 + synthesizer/src/vm/deploy.rs | 53 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index a0c3d685a7..341c74f447 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -212,6 +212,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { } /// Removes the deployment transaction for the given `transaction ID`. + // TODO (@d0cd) This removal needs to be done with the reverse map. fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> { // Retrieve the program ID. let program_id = match self.get_program_id(transaction_id)? { diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index be69ad2187..223bcc0cfc 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -66,6 +66,59 @@ impl> VM { // Return the deploy transaction. Transaction::from_deployment(owner, deployment, fee) } + + /// Returns a new deploy transaction with an authority address. + /// The authority address is allowed to update the program in a subsequent deployment. + /// + /// If a `fee_record` is provided, then a private fee will be included in the transaction; + /// otherwise, a public fee will be included in the transaction. + /// + /// The `priority_fee_in_microcredits` is an additional fee **on top** of the deployment fee. + pub fn deploy_with_authority( + &self, + private_key: &PrivateKey, + authority: Address, + program: &Program, + fee_record: Option>>, + priority_fee_in_microcredits: u64, + query: Option>, + rng: &mut R, + ) -> Result> { + // Compute the deployment. + let deployment = self.deploy_raw(program, rng)?; + // Ensure the transaction is not empty. + ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty transaction deployment"); + // Compute the deployment ID. + let deployment_id = deployment.to_deployment_id()?; + // Construct the owner with authority. + let owner = ProgramOwner::new_v2(private_key, authority, deployment_id, rng)?; + + // Compute the minimum deployment cost. + let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; + // Authorize the fee. + let fee_authorization = match fee_record { + Some(record) => self.authorize_fee_private( + private_key, + record, + minimum_deployment_cost, + priority_fee_in_microcredits, + deployment_id, + rng, + )?, + None => self.authorize_fee_public( + private_key, + minimum_deployment_cost, + priority_fee_in_microcredits, + deployment_id, + rng, + )?, + }; + // Compute the fee. + let fee = self.execute_fee_authorization(fee_authorization, query, rng)?; + + // Return the deploy transaction. + Transaction::from_deployment(owner, deployment, fee) + } } impl> VM { From 740fdc9c5809ac096d65a6614cb0a5dda4100c39 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:06:42 -0800 Subject: [PATCH 04/46] Clippy --- synthesizer/src/vm/deploy.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index 223bcc0cfc..bd26cfb707 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -74,6 +74,7 @@ impl> VM { /// otherwise, a public fee will be included in the transaction. /// /// The `priority_fee_in_microcredits` is an additional fee **on top** of the deployment fee. + #[allow(clippy::too_many_arguments)] pub fn deploy_with_authority( &self, private_key: &PrivateKey, From 9bae55b4b9c286ac118516d65056bcadf0e9e1fb Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:35:50 -0800 Subject: [PATCH 05/46] Add skeleton for finalizing updates --- ledger/store/src/transaction/deployment.rs | 5 + synthesizer/process/src/finalize.rs | 54 ++++++++ synthesizer/src/vm/finalize.rs | 137 +++++++++++++++------ synthesizer/src/vm/verify.rs | 56 +++++++-- 4 files changed, 208 insertions(+), 44 deletions(-) diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index 341c74f447..683f7861fa 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -577,6 +577,11 @@ impl> DeploymentStore { self.storage.get_edition(program_id) } + /// Returns the owner for the given `program ID`. + pub fn get_owner(&self, program_id: &ProgramID) -> Result>> { + self.storage.get_owner(program_id) + } + /// Returns the program ID for the given `transaction ID`. pub fn get_program_id(&self, transaction_id: &N::TransactionID) -> Result>> { self.storage.get_program_id(transaction_id) diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index f5b7e18c08..542e88dd90 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -22,6 +22,7 @@ use std::collections::HashSet; impl Process { /// Finalizes the deployment and fee. + // TODO (@d0cd) We should specify what valid here means, because `Stack::new` performs a number of checks /// This method assumes the given deployment **is valid**. /// This method should **only** be called by `VM::finalize()`. #[inline] @@ -73,6 +74,59 @@ impl Process { }) } + /// Finalizes the deployment and fee. + // TODO (@d0cd) We should specify what valid here means, because `Stack::new` performs a number of checks + /// This method assumes the given deployment **is valid**. + /// This method should **only** be called by `VM::finalize()`. + #[inline] + pub fn finalize_update>( + &self, + state: FinalizeGlobalState, + store: &FinalizeStore, + deployment: &Deployment, + fee: &Fee, + ) -> Result<(Stack, Vec>)> { + let timer = timer!("Process::finalize_deployment"); + + // Compute the program stack. + let stack = Stack::new(self, deployment.program())?; + lap!(timer, "Compute the stack"); + + // Insert the verifying keys. + for (function_name, (verifying_key, _)) in deployment.verifying_keys() { + stack.insert_verifying_key(function_name, verifying_key.clone())?; + } + lap!(timer, "Insert the verifying keys"); + + // Initialize the mappings, and store their finalize operations. + atomic_batch_scope!(store, { + // Initialize a list for the finalize operations. + let mut finalize_operations = Vec::with_capacity(deployment.program().mappings().len()); + + /* Finalize the fee. */ + + // Retrieve the fee stack. + let fee_stack = self.get_stack(fee.program_id())?; + // Finalize the fee transition. + finalize_operations.extend(finalize_fee_transition(state, store, fee_stack, fee)?); + lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name()); + + /* Finalize the deployment. */ + + // Retrieve the program ID. + let program_id = deployment.program_id(); + // Iterate over the mappings. + for mapping in deployment.program().mappings().values() { + // Initialize the mapping. + finalize_operations.push(store.initialize_mapping(*program_id, *mapping.name())?); + } + finish!(timer, "Initialize the program mappings"); + + // Return the stack and finalize operations. + Ok((stack, finalize_operations)) + }) + } + /// Finalizes the execution and fee. /// This method assumes the given execution **is valid**. /// This method should **only** be called by `VM::finalize()`. diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index dda3f3137b..f9a9e2f87f 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -365,28 +365,66 @@ impl> VM { } }, // If the program has not yet been deployed, attempt to deploy it. - false => match process.finalize_deployment(state, store, deployment, fee) { - // Construct the accepted deploy transaction. - Ok((_, finalize)) => { - // Add the program id to the list of deployments. - deployments.insert(*deployment.program_id()); - ConfirmedTransaction::accepted_deploy(counter, transaction.clone(), finalize) - .map_err(|e| e.to_string()) - } - // Construct the rejected deploy transaction. - Err(_error) => match process_rejected_deployment(fee, *deployment.clone()) { - Ok(result) => result, - Err(error) => { - // Note: On failure, skip this transaction, and continue speculation. - #[cfg(debug_assertions)] - eprintln!("Failed to finalize the fee in a rejected deploy - {error}"); - // Store the aborted transaction. - aborted.push((transaction.clone(), error.to_string())); - // Continue to the next transaction. - continue 'outer; + false => { + // Handle initial deployments and updates via `finalize_deployment` and `finalize_update` respectively. + // TODO (@d0cd) Dedup logic + let is_initial_deployment = deployment.edition() == 0; + if is_initial_deployment { + match process.finalize_deployment(state, store, deployment, fee) { + // Construct the accepted deploy transaction. + Ok((_, finalize)) => { + // Add the program id to the list of deployments. + deployments.insert(*deployment.program_id()); + ConfirmedTransaction::accepted_deploy( + counter, + transaction.clone(), + finalize, + ) + .map_err(|e| e.to_string()) + } + // Construct the rejected deploy transaction. + Err(_error) => match process_rejected_deployment(fee, *deployment.clone()) { + Ok(result) => result, + Err(error) => { + // Note: On failure, skip this transaction, and continue speculation. + #[cfg(debug_assertions)] + eprintln!("Failed to finalize the fee in a rejected deploy - {error}"); + // Store the aborted transaction. + aborted.push((transaction.clone(), error.to_string())); + // Continue to the next transaction. + continue 'outer; + } + }, } - }, - }, + } else { + match process.finalize_update(state, store, deployment, fee) { + // Construct the accepted deploy transaction. + Ok((_, finalize)) => { + // Add the program id to the list of deployments. + deployments.insert(*deployment.program_id()); + ConfirmedTransaction::accepted_deploy( + counter, + transaction.clone(), + finalize, + ) + .map_err(|e| e.to_string()) + } + // Construct the rejected deploy transaction. + Err(_error) => match process_rejected_deployment(fee, *deployment.clone()) { + Ok(result) => result, + Err(error) => { + // Note: On failure, skip this transaction, and continue speculation. + #[cfg(debug_assertions)] + eprintln!("Failed to finalize the fee in a rejected deploy - {error}"); + // Store the aborted transaction. + aborted.push((transaction.clone(), error.to_string())); + // Continue to the next transaction. + continue 'outer; + } + }, + } + } + } } } // The finalize operation here involves calling 'update_key_value', @@ -614,24 +652,49 @@ impl> VM { // Note: This will abort the entire atomic batch. _ => return Err("Expected deploy transaction".to_string()), }; - // The finalize operation here involves appending the 'stack', and adding the program to the finalize tree. - match process.finalize_deployment(state, store, deployment, fee) { - // Ensure the finalize operations match the expected. - Ok((stack, finalize_operations)) => match finalize == &finalize_operations { - // Store the stack. - true => stacks.push(stack), + // Handle initial deployments and updates via `finalize_deployment` and `finalize_update` respectively. + // TODO (@d0cd) Dedup logic + let is_initial_deployment = deployment.edition() == 0; + if is_initial_deployment { + // The finalize operation here involves appending the 'stack', and adding the program to the finalize tree. + match process.finalize_deployment(state, store, deployment, fee) { + // Ensure the finalize operations match the expected. + Ok((stack, finalize_operations)) => match finalize == &finalize_operations { + // Store the stack. + true => stacks.push(stack), + // Note: This will abort the entire atomic batch. + false => { + return Err(format!( + "Mismatch in finalize operations for an accepted deploy - (found: {finalize_operations:?}, expected: {finalize:?})" + )); + } + }, // Note: This will abort the entire atomic batch. - false => { - return Err(format!( - "Mismatch in finalize operations for an accepted deploy - (found: {finalize_operations:?}, expected: {finalize:?})" - )); + Err(error) => { + return Err(format!("Failed to finalize an accepted deploy transaction - {error}")); } - }, - // Note: This will abort the entire atomic batch. - Err(error) => { - return Err(format!("Failed to finalize an accepted deploy transaction - {error}")); - } - }; + }; + } else { + // The finalize operation here involves updating the 'stack', and updating the program in the finalize tree. + match process.finalize_update(state, store, deployment, fee) { + // Ensure the finalize operations match the expected. + Ok((stack, finalize_operations)) => match finalize == &finalize_operations { + // Store the stack. + true => stacks.push(stack), + // Note: This will abort the entire atomic batch. + false => { + return Err(format!( + "Mismatch in finalize operations for an accepted deploy - (found: {finalize_operations:?}, expected: {finalize:?})" + )); + } + }, + // Note: This will abort the entire atomic batch. + Err(error) => { + return Err(format!("Failed to finalize an accepted deploy transaction - {error}")); + } + }; + } + Ok(()) } ConfirmedTransaction::AcceptedExecute(_, transaction, finalize) => { diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 478350c748..c2107c6090 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -155,14 +155,56 @@ impl> VM { if deployment.edition() != N::EDITION { bail!("Invalid deployment transaction '{id}' - expected edition {}", N::EDITION) } - // Ensure the program ID does not already exist in the store. - if self.transaction_store().contains_program_id(deployment.program_id())? { - bail!("Program ID '{}' is already deployed", deployment.program_id()) - } - // Ensure the program does not already exist in the process. - if self.contains_program(deployment.program_id()) { - bail!("Program ID '{}' already exists", deployment.program_id()); + // TODO (@d0cd) This check may not be needed here. + // If the owner is a V1 owner, then check that: + // - The program ID does not already exist in the store. + // - The program does not already exist in the process. + // Otherwise, if the owner is a V2 owner, then either: + // - The program ID does not already exist in the store and process. + // - The program ID exists in the store and process, and the new owner address matches the authority address. + let store_contains_program = self.transaction_store().contains_program_id(deployment.program_id())?; + let process_contains_program = self.contains_program(deployment.program_id()); + match &owner { + ProgramOwner::V1(_) => { + ensure!( + !store_contains_program, + "Program ID '{}' is already deployed", + deployment.program_id() + ); + ensure!(!process_contains_program, "Program ID '{}' already exists", deployment.program_id()); + } + ProgramOwner::V2(owner) => { + match (store_contains_program, process_contains_program) { + (false, false) => {} // Do nothing as the program is being deployed for the first time. + (true, true) => { + match self.transaction_store().deployment_store().get_owner(deployment.program_id())? { + Some(ProgramOwner::V2(old_owner)) => ensure!( + owner.address() == old_owner.authority(), + "Invalid authority for the program ID '{}' - expected authority address to be '{}'", + deployment.program_id(), + owner.authority() + ), + _ => bail!( + "Invalid program owner for the program ID '{:?}', expected a V2 program owner", + deployment.program_id() + ), + } + } + // TODO (@d0cd): Is this the correct failure mode? + _ => bail!( + "Inconsistent VM and storage state for the program ID '{:?}'", + deployment.program_id() + ), + } + ensure!( + !store_contains_program, + "Program ID '{}' is already deployed", + deployment.program_id() + ); + ensure!(!process_contains_program, "Program ID '{}' already exists", deployment.program_id()); + } } + // Verify the deployment if it has not been verified before. if !is_partially_verified { // Verify the deployment. From 5eefdcf2bafe43636d8371e3b14ad907ed1122ce Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:56:02 -0800 Subject: [PATCH 06/46] Use editions to track versions of the program --- console/program/src/owner/bytes.rs | 6 +- console/program/src/owner/mod.rs | 36 ++++++-- console/program/src/owner/serialize.rs | 6 +- .../block/src/transaction/deployment/mod.rs | 13 ++- synthesizer/src/vm/deploy.rs | 11 ++- synthesizer/src/vm/verify.rs | 87 ++++++++++++------- 6 files changed, 109 insertions(+), 50 deletions(-) diff --git a/console/program/src/owner/bytes.rs b/console/program/src/owner/bytes.rs index 35bcf04883..54540c6d88 100644 --- a/console/program/src/owner/bytes.rs +++ b/console/program/src/owner/bytes.rs @@ -36,11 +36,13 @@ impl FromBytes for ProgramOwner { let address = Address::read_le(&mut reader)?; // Read the authority. let authority = Address::read_le(&mut reader)?; + // Read the edition. + let edition = U16::read_le(&mut reader)?; // Read the signature. let signature = Signature::read_le(&mut reader)?; // Return the program owner. - Ok(Self::V2(ProgramOwnerV2::from(address, authority, signature))) + Ok(Self::V2(ProgramOwnerV2::from(address, authority, edition, signature))) } _ => Err(error("Invalid program owner version")), } @@ -66,6 +68,8 @@ impl ToBytes for ProgramOwner { owner.address.write_le(&mut writer)?; // Write the authority. owner.authority.write_le(&mut writer)?; + // Write the edition. + owner.edition.write_le(&mut writer)?; // Write the signature. owner.signature.write_le(&mut writer) } diff --git a/console/program/src/owner/mod.rs b/console/program/src/owner/mod.rs index a5b204f524..97e0a2f259 100644 --- a/console/program/src/owner/mod.rs +++ b/console/program/src/owner/mod.rs @@ -44,10 +44,11 @@ impl ProgramOwner { pub fn new_v2( private_key: &PrivateKey, authority: Address, + edition: U16, deployment_id: Field, rng: &mut R, ) -> Result { - Ok(Self::V2(ProgramOwnerV2::new(private_key, authority, deployment_id, rng)?)) + Ok(Self::V2(ProgramOwnerV2::new(private_key, authority, edition, deployment_id, rng)?)) } /// Returns the program owner as a V1 owner. @@ -159,6 +160,8 @@ pub struct ProgramOwnerV2 { address: Address, /// The address of the authority allowed to update the program. authority: Address, + /// The edition of the program. + edition: U16, /// The signature of the program owner, over the deployment transaction ID. signature: Signature, } @@ -168,20 +171,22 @@ impl ProgramOwnerV2 { pub fn new( private_key: &PrivateKey, authority: Address, + edition: U16, deployment_id: Field, rng: &mut R, ) -> Result { // Derive the address. let address = Address::try_from(private_key)?; // Sign the transaction ID. - let signature = private_key.sign(&[authority.to_x_coordinate(), deployment_id], rng)?; + let signature = + private_key.sign(&[authority.to_x_coordinate(), Field::from_u16(*edition), deployment_id], rng)?; // Return the program owner. - Ok(Self { address, authority, signature }) + Ok(Self { address, authority, edition, signature }) } /// Initializes a new V2 program owner from an address, authority, and signature. - pub fn from(address: Address, authority: Address, signature: Signature) -> Self { - Self { address, authority, signature } + pub fn from(address: Address, authority: Address, edition: U16, signature: Signature) -> Self { + Self { address, authority, edition, signature } } /// Returns the address of the V2 program owner. @@ -194,6 +199,11 @@ impl ProgramOwnerV2 { &self.authority } + /// Returns the edition of the V2 program owner. + pub const fn edition(&self) -> U16 { + self.edition + } + /// Returns the signature of the V2 program owner. pub const fn signature(&self) -> &Signature { &self.signature @@ -201,7 +211,11 @@ impl ProgramOwnerV2 { /// Verify that the signature is valid for the given deployment ID. pub fn verify(&self, deployment_id: Field) -> bool { - self.signature.verify(&self.address, &[self.authority.to_x_coordinate(), deployment_id]) + self.signature.verify(&self.address, &[ + self.authority.to_x_coordinate(), + Field::from_u16(*self.edition), + deployment_id, + ]) } } @@ -243,11 +257,14 @@ pub(crate) mod test_helpers { // Initialize an authority. let authority = Address::::try_from(&private_key).unwrap(); + // Initialize an edition. + let edition = U16::::rand(rng); + // Initialize a deployment ID. let deployment_id: Field = rng.gen(); // Return the program owner. - ProgramOwner::new_v2(&private_key, authority, deployment_id, rng).unwrap() + ProgramOwner::new_v2(&private_key, authority, edition, deployment_id, rng).unwrap() }) } @@ -283,11 +300,14 @@ pub(crate) mod test_helpers { // Initialize an authority. let authority = Address::::try_from(&private_key).unwrap(); + // Initialize an edition. + let edition = U16::::rand(rng); + // Initialize a deployment ID. let deployment_id: Field = rng.gen(); // Construct the program owner. - let owner = ProgramOwner::new_v2(&private_key, authority, deployment_id, rng).unwrap(); + let owner = ProgramOwner::new_v2(&private_key, authority, edition, deployment_id, rng).unwrap(); // Ensure that the program owner is verified for the given deployment ID. assert!(owner.verify(deployment_id)); diff --git a/console/program/src/owner/serialize.rs b/console/program/src/owner/serialize.rs index d41c7a7549..0f2f54b673 100644 --- a/console/program/src/owner/serialize.rs +++ b/console/program/src/owner/serialize.rs @@ -30,9 +30,10 @@ impl Serialize for ProgramOwner { owner.end() } Self::V2(program_owner_v2) => { - let mut owner = serializer.serialize_struct("ProgramOwnerV2", 3)?; + let mut owner = serializer.serialize_struct("ProgramOwnerV2", 4)?; owner.serialize_field("address", &program_owner_v2.address())?; owner.serialize_field("authority", &program_owner_v2.authority())?; + owner.serialize_field("edition", &program_owner_v2.edition())?; owner.serialize_field("signature", &program_owner_v2.signature())?; owner.end() } @@ -52,8 +53,9 @@ impl<'de, N: Network> Deserialize<'de> for ProgramOwner { // If the `authority` field is present, then use the V2 format. let address = DeserializeExt::take_from_value::(&mut owner, "address")?; let authority = DeserializeExt::take_from_value::(&mut owner, "authority")?; + let edition = DeserializeExt::take_from_value::(&mut owner, "edition")?; let signature = DeserializeExt::take_from_value::(&mut owner, "signature")?; - Ok(Self::V2(ProgramOwnerV2::from(address, authority, signature))) + Ok(Self::V2(ProgramOwnerV2::from(address, authority, edition, signature))) } else { // Otherwise, use the V1 format. let address = DeserializeExt::take_from_value::(&mut owner, "address")?; diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index 42c6182f6f..a412080102 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -57,13 +57,6 @@ impl Deployment { pub fn check_is_ordered(&self) -> Result<()> { let program_id = self.program.id(); - // Ensure the edition matches. - ensure!( - self.edition == N::EDITION, - "Deployed the wrong edition (expected '{}', found '{}').", - N::EDITION, - self.edition - ); // Ensure the program contains functions. ensure!( !self.program.functions().is_empty(), @@ -163,6 +156,12 @@ impl Deployment { pub fn to_deployment_id(&self) -> Result> { Ok(*Transaction::deployment_tree(self, None)?.root()) } + + // TODO (@d0cd) Contemplate design. + /// Updates the deployment edition. + pub fn update_edition(&mut self, edition: u16) { + self.edition = edition; + } } #[cfg(test)] diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index bd26cfb707..64c4358bfe 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -14,6 +14,7 @@ // limitations under the License. use super::*; +use console::types::U16; impl> VM { /// Returns a new deploy transaction. @@ -75,10 +76,12 @@ impl> VM { /// /// The `priority_fee_in_microcredits` is an additional fee **on top** of the deployment fee. #[allow(clippy::too_many_arguments)] - pub fn deploy_with_authority( + // TODO (@d0cd) Better name. + pub fn deploy_with_authority_and_edition( &self, private_key: &PrivateKey, authority: Address, + edition: U16, program: &Program, fee_record: Option>>, priority_fee_in_microcredits: u64, @@ -86,13 +89,15 @@ impl> VM { rng: &mut R, ) -> Result> { // Compute the deployment. - let deployment = self.deploy_raw(program, rng)?; + let mut deployment = self.deploy_raw(program, rng)?; + // Update the edition. + deployment.update_edition(*edition); // Ensure the transaction is not empty. ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty transaction deployment"); // Compute the deployment ID. let deployment_id = deployment.to_deployment_id()?; // Construct the owner with authority. - let owner = ProgramOwner::new_v2(private_key, authority, deployment_id, rng)?; + let owner = ProgramOwner::new_v2(private_key, authority, edition, deployment_id, rng)?; // Compute the minimum deployment cost. let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index c2107c6090..dcd6a71849 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -151,21 +151,23 @@ impl> VM { }; // Verify the signature corresponds to the transaction ID. ensure!(owner.verify(deployment_id), "Invalid owner signature for deployment transaction '{id}'"); - // Ensure the edition is correct. - if deployment.edition() != N::EDITION { - bail!("Invalid deployment transaction '{id}' - expected edition {}", N::EDITION) - } - // TODO (@d0cd) This check may not be needed here. + // If the owner is a V1 owner, then check that: - // - The program ID does not already exist in the store. - // - The program does not already exist in the process. - // Otherwise, if the owner is a V2 owner, then either: - // - The program ID does not already exist in the store and process. - // - The program ID exists in the store and process, and the new owner address matches the authority address. + // - The deployment's edition matches the network's edition. + // - The program does not exist in the store or process. + // Otherwise, if the owner is a V2 owner, then check that: + // - The deployment's edition matches the owner's edition. + // - If the edition is 0, then program ID does not exist in the store or process. + // - If the edition is not zero, then the program ID exists in the store and process, the new owner matches the old authority, and the new edition increments the old edition. let store_contains_program = self.transaction_store().contains_program_id(deployment.program_id())?; let process_contains_program = self.contains_program(deployment.program_id()); match &owner { ProgramOwner::V1(_) => { + ensure!( + deployment.edition() == N::EDITION, + "Invalid deployment transaction '{id}' - expected edition {}", + N::EDITION + ); ensure!( !store_contains_program, "Program ID '{}' is already deployed", @@ -174,34 +176,61 @@ impl> VM { ensure!(!process_contains_program, "Program ID '{}' already exists", deployment.program_id()); } ProgramOwner::V2(owner) => { - match (store_contains_program, process_contains_program) { - (false, false) => {} // Do nothing as the program is being deployed for the first time. - (true, true) => { + ensure!( + deployment.edition() == *owner.edition(), + "Invalid deployment transaction '{id}' - deployment and owner editions do not match" + ); + match deployment.edition() { + 0 => { + ensure!( + !store_contains_program, + "Program ID '{}' is already deployed", + deployment.program_id() + ); + ensure!( + !process_contains_program, + "Program ID '{}' already exists", + deployment.program_id() + ); + } + edition => { + // Ensure the program exists in the store. + ensure!( + store_contains_program, + "Invalid deployment transaction '{id}' - program does not exist in the store" + ); + // Ensure the program exists in the process. + ensure!( + process_contains_program, + "Invalid deployment transaction '{id}' - program does not exist in the process" + ); + // Ensure the new owner matches the old authority. match self.transaction_store().deployment_store().get_owner(deployment.program_id())? { Some(ProgramOwner::V2(old_owner)) => ensure!( - owner.address() == old_owner.authority(), - "Invalid authority for the program ID '{}' - expected authority address to be '{}'", - deployment.program_id(), - owner.authority() + old_owner.authority() == owner.address(), + "Invalid deployment transaction '{id}' - new owner does not match old authority" ), _ => bail!( - "Invalid program owner for the program ID '{:?}', expected a V2 program owner", + "Invalid deployment transaction '{id}' - expected V2 owner for program ID '{}'", deployment.program_id() ), } + // Ensure the new edition increments the old edition. + match self + .transaction_store() + .deployment_store() + .get_edition(deployment.program_id())? + { + Some(old_edition) => ensure!( + old_edition < edition && old_edition.saturating_add(1) == edition, + "Invalid deployment transaction '{id}' - new edition does not increment old edition" + ), + None => bail!( + "Invalid deployment transaction '{id}' - program does not exist in the store" + ), + } } - // TODO (@d0cd): Is this the correct failure mode? - _ => bail!( - "Inconsistent VM and storage state for the program ID '{:?}'", - deployment.program_id() - ), } - ensure!( - !store_contains_program, - "Program ID '{}' is already deployed", - deployment.program_id() - ); - ensure!(!process_contains_program, "Program ID '{}' already exists", deployment.program_id()); } } From 73c2db7524d50cecb7ea8fa6e684647bf098a6b8 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 23 Jan 2025 19:29:10 -0800 Subject: [PATCH 07/46] Checkpointing design --- ledger/store/src/transaction/deployment.rs | 6 + ledger/store/src/transaction/mod.rs | 8 +- synthesizer/process/src/finalize.rs | 10 +- .../process/src/stack/helpers/check_update.rs | 104 ++++++++++++++++++ synthesizer/process/src/stack/helpers/mod.rs | 1 + synthesizer/src/vm/mod.rs | 3 + 6 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 synthesizer/process/src/stack/helpers/check_update.rs diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index 683f7861fa..242bc20cbb 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -183,6 +183,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { // Retrieve the program ID. let program_id = *program.id(); + // TODO (@d0cd) Should we add a safety check for incremental update on the edition map? + atomic_batch_scope!(self, { // Store the program ID. self.id_map().insert(*transaction_id, program_id)?; @@ -259,6 +261,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { } /// Returns the transaction ID that contains the given `program ID`. + // TODO (@d0cd) Define semantics, OG deployment or latest? fn find_transaction_id_from_program_id(&self, program_id: &ProgramID) -> Result> { // Check if the program ID is for 'credits.aleo'. // This case is handled separately, as it is a default program of the VM. @@ -390,6 +393,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { } /// Returns the deployment for the given `transaction ID`. + // TODO (@d0cd) This should be done with IDMapV2. fn get_deployment(&self, transaction_id: &N::TransactionID) -> Result>> { // Retrieve the program ID. let program_id = match self.get_program_id(transaction_id)? { @@ -645,6 +649,7 @@ impl> DeploymentStore { } /// Returns an iterator over the program IDs, for all deployments. + // TODO (@d0cd) This can have duplicates. pub fn program_ids(&self) -> impl '_ + Iterator>> { self.storage.id_map().values_confirmed().map(|id| match id { Cow::Borrowed(id) => Cow::Borrowed(id), @@ -653,6 +658,7 @@ impl> DeploymentStore { } /// Returns an iterator over the programs, for all deployments. + // TODO (@d0cd) This would contain all versions of the program pub fn programs(&self) -> impl '_ + Iterator>> { self.storage.program_map().values_confirmed().map(|program| match program { Cow::Borrowed(program) => Cow::Borrowed(program), diff --git a/ledger/store/src/transaction/mod.rs b/ledger/store/src/transaction/mod.rs index 1bb6966e1d..6bac5409ec 100644 --- a/ledger/store/src/transaction/mod.rs +++ b/ledger/store/src/transaction/mod.rs @@ -366,13 +366,7 @@ impl> TransactionStore { // Retrieve the edition. match transaction_type { TransactionType::Deploy => { - // Retrieve the program ID. - let program_id = self.storage.deployment_store().get_program_id(transaction_id)?; - // Return the edition. - match program_id { - Some(program_id) => self.storage.deployment_store().get_edition(&program_id), - None => bail!("Failed to get the program ID for deployment transaction '{transaction_id}'"), - } + todo!("@d0cd Use IDMapV2") } // Return 'None'. TransactionType::Execute => Ok(None), diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 542e88dd90..b17681c25b 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -88,9 +88,17 @@ impl Process { ) -> Result<(Stack, Vec>)> { let timer = timer!("Process::finalize_deployment"); + // Get the existing stack. + let old_stack = self.get_stack(deployment.program_id())?; + + // Check that the update is valid. + old_stack.check_update(deployment.program())?; + // Compute the program stack. let stack = Stack::new(self, deployment.program())?; - lap!(timer, "Compute the stack"); + lap!(timer, "Update the stack"); + + // TODO (@d0cd) Add the mappings. // Insert the verifying keys. for (function_name, (verifying_key, _)) in deployment.verifying_keys() { diff --git a/synthesizer/process/src/stack/helpers/check_update.rs b/synthesizer/process/src/stack/helpers/check_update.rs new file mode 100644 index 0000000000..7c69c86683 --- /dev/null +++ b/synthesizer/process/src/stack/helpers/check_update.rs @@ -0,0 +1,104 @@ +// Copyright 2024 Aleo Network Foundation +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +impl Stack { + /// Updates an existing stack, given the process and program. + #[inline] + pub(crate) fn check_update(&self, program: &Program) -> Result<()> { + // Get the old program. + let old_program = self.program(); + // Ensure the program ID matches. + ensure!(old_program.id() == program.id(), "Cannot update program with different program ID"); + + // Ensure that all of the structs in the old program exist in the new program. + for (struct_id, struct_type) in old_program.structs() { + let new_struct_type = program.get_struct(struct_id)?; + ensure!( + struct_type == new_struct_type, + "Cannot update program because the struct '{struct_id}' has different types" + ); + } + // Ensure that all of the records in the old program exist in the new program. + for (record_id, record_type) in old_program.records() { + let new_record_type = program.get_record(record_id)?; + ensure!( + record_type == new_record_type, + "Cannot update program because the record '{record_id}' has different types" + ); + } + // Ensure that all of the mappings in the old program exist in the new program. + for (mapping_id, mapping_type) in old_program.mappings() { + let new_mapping_type = program.get_mapping(mapping_id)?; + ensure!( + *mapping_type == new_mapping_type, + "Cannot update program because the mapping '{mapping_id}' has different types" + ); + } + // Ensure that all of the imports in the old program exist in the new program. + for import in old_program.imports().keys() { + if !program.contains_import(import) { + bail!("Cannot update program because it is missing the import '{import}'"); + } + } + // Ensure that the old program closures exist in the new program, with the same input and output types. + for closure in old_program.closures().values() { + if !program.contains_closure(closure.name()) { + bail!("Cannot update program because it is missing the closure '{closure}'"); + } + let new_closure = program.get_closure(closure.name())?; + ensure!( + closure.inputs() == new_closure.inputs(), + "Cannot update program because the closure '{closure}' has different input types" + ); + ensure!( + closure.outputs() == new_closure.outputs(), + "Cannot update program because the closure '{closure}' has different output types" + ); + } + // Ensure that the old program functions exist in the new program, with the same input and output types. + // If the function has an associated `finalize` block, then ensure that the finalize block exists in the new program. + for function in old_program.functions().values() { + if !program.contains_function(function.name()) { + bail!("Cannot update program because it is missing the function '{function}'"); + } + let new_function = program.get_function(function.name())?; + ensure!( + function.inputs() == new_function.inputs(), + "Cannot update program because the function '{function}' has different input types" + ); + ensure!( + function.outputs() == new_function.outputs(), + "Cannot update program because the function '{function}' has different output types" + ); + if let Some(finalize) = function.finalize_logic() { + match new_function.finalize_logic() { + Some(new_finalize) => { + ensure!( + finalize.inputs() == new_finalize.inputs(), + "Cannot update program because the finalize block '{finalize}' has different input types" + ); + } + None => { + bail!("Cannot update program because the function '{function}' is missing a finalize block") + } + } + } + } + + Ok(()) + } +} diff --git a/synthesizer/process/src/stack/helpers/mod.rs b/synthesizer/process/src/stack/helpers/mod.rs index b5988c77b1..a8ea14a1bc 100644 --- a/synthesizer/process/src/stack/helpers/mod.rs +++ b/synthesizer/process/src/stack/helpers/mod.rs @@ -15,6 +15,7 @@ use super::*; +mod check_update; mod initialize; mod matches; mod sample; diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index aed27fb2bc..5af5d83124 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -180,8 +180,11 @@ impl> VM { for (program_id, deployment) in deployments.iter().flatten() { // Load the deployment if it does not exist in the process yet. + // Otherwise, update the existing program with the new deployment. if !process.contains_program(program_id) { process.load_deployment(deployment)?; + } else { + todo!("@d0cd") } } } From 66c71436fe81953d58bef7635e6ab6a81d158341 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 24 Jan 2025 18:46:27 -0800 Subject: [PATCH 08/46] Add editions to process stacks --- ledger/benches/transaction.rs | 2 +- .../puzzle/epoch/src/synthesis/program/mod.rs | 2 +- ledger/src/tests.rs | 2 +- ledger/test-helpers/src/lib.rs | 2 +- .../process/benches/stack_operations.rs | 6 +-- synthesizer/process/src/deploy.rs | 5 ++- synthesizer/process/src/finalize.rs | 4 +- synthesizer/process/src/lib.rs | 42 ++++++++++++------- .../process/src/stack/helpers/initialize.rs | 3 +- synthesizer/process/src/stack/mod.rs | 12 +++++- synthesizer/process/src/tests/test_execute.rs | 37 ++++++++-------- synthesizer/process/src/verify_deployment.rs | 2 +- .../src/logic/instruction/operation/async_.rs | 2 +- .../program/src/traits/stack_and_registers.rs | 3 ++ .../program/tests/instruction/assert.rs | 2 +- .../program/tests/instruction/commit.rs | 2 +- synthesizer/program/tests/instruction/hash.rs | 2 +- synthesizer/program/tests/instruction/is.rs | 2 +- synthesizer/tests/test_process_execute.rs | 2 +- vm/file/prover.rs | 2 +- vm/file/verifier.rs | 2 +- vm/package/deploy.rs | 2 +- vm/package/mod.rs | 4 +- 23 files changed, 87 insertions(+), 57 deletions(-) diff --git a/ledger/benches/transaction.rs b/ledger/benches/transaction.rs index 933b0918c0..194174544c 100644 --- a/ledger/benches/transaction.rs +++ b/ledger/benches/transaction.rs @@ -257,7 +257,7 @@ function main: let inputs = [Value::from_str("2group").unwrap()].into_iter(); // Add the program to the VM. - vm.process().write().add_program(&program).unwrap(); + vm.process().write().add_program(&program, 0).unwrap(); // Create an execution transaction that is 164613 bytes in size. let transaction = vm.execute(&private_key, ("too_big.aleo", "main"), inputs, None, 0, None, rng).unwrap(); diff --git a/ledger/puzzle/epoch/src/synthesis/program/mod.rs b/ledger/puzzle/epoch/src/synthesis/program/mod.rs index d1a1ac2298..f896957fe7 100644 --- a/ledger/puzzle/epoch/src/synthesis/program/mod.rs +++ b/ledger/puzzle/epoch/src/synthesis/program/mod.rs @@ -106,7 +106,7 @@ function synthesize: // Initialize a new process. let process = Process::::load()?; // Initialize the stack with the synthesis challenge program. - let stack = Stack::new(&process, &program)?; + let stack = Stack::new(&process, &program, 0)?; Ok(Self { stack, register_table, epoch_hash }) } diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index d3aacbd33c..cf4ba0dde2 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -2183,7 +2183,7 @@ fn test_deployment_exceeding_max_transaction_spend() { // Attempt to initialize a `Stack` for the program. // If this fails, then by `Stack::initialize` the finalize cost exceeds the `TRANSACTION_SPEND_LIMIT`. - if Stack::::new(&ledger.vm().process().read(), &program).is_err() { + if Stack::::new(&ledger.vm().process().read(), &program, 0).is_err() { exceeding_program = Some(program); break; } else { diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index 1ae367b19a..4d15d9541b 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -401,7 +401,7 @@ pub fn sample_large_execution_transaction(rng: &mut TestRng) -> Transaction::new(&process, program), + |program| Stack::::new(&process, program, 0), BatchSize::PerIteration, ) }); @@ -86,7 +86,7 @@ fn bench_stack_new(c: &mut Criterion) { )) .unwrap() }, - |program| Stack::::new(&process, program), + |program| Stack::::new(&process, program, 0), BatchSize::PerIteration, ) }); @@ -150,7 +150,7 @@ fn add_program_at_depth(process: &mut Process, depth: usize) { }; // Add the program to the process. - process.add_program(&program).unwrap(); + process.add_program(&program, 0).unwrap(); } // Samples a random identifier as a string. diff --git a/synthesizer/process/src/deploy.rs b/synthesizer/process/src/deploy.rs index 88ddb829fd..e9bd276624 100644 --- a/synthesizer/process/src/deploy.rs +++ b/synthesizer/process/src/deploy.rs @@ -26,7 +26,8 @@ impl Process { let timer = timer!("Process::deploy"); // Compute the stack. - let stack = Stack::new(self, program)?; + // TODO (@d0cd) Should we be using a zero edition? + let stack = Stack::new(self, program, 0)?; lap!(timer, "Compute the stack"); // Return the deployment. @@ -45,7 +46,7 @@ impl Process { let timer = timer!("Process::load_deployment"); // Compute the program stack. - let stack = Stack::new(self, deployment.program())?; + let stack = Stack::new(self, deployment.program(), deployment.edition())?; lap!(timer, "Compute the stack"); // Insert the verifying keys. diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index b17681c25b..f78ae50733 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -36,7 +36,7 @@ impl Process { let timer = timer!("Process::finalize_deployment"); // Compute the program stack. - let stack = Stack::new(self, deployment.program())?; + let stack = Stack::new(self, deployment.program(), deployment.edition())?; lap!(timer, "Compute the stack"); // Insert the verifying keys. @@ -95,7 +95,7 @@ impl Process { old_stack.check_update(deployment.program())?; // Compute the program stack. - let stack = Stack::new(self, deployment.program())?; + let stack = Stack::new(self, deployment.program(), deployment.edition())?; lap!(timer, "Update the stack"); // TODO (@d0cd) Add the mappings. diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index 85185f39f8..d0356d1d69 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -79,7 +79,9 @@ pub struct Process { /// The universal SRS. universal_srs: Arc>, /// The mapping of program IDs to stacks. - stacks: IndexMap, Arc>>, + stacks: IndexMap<(ProgramID, u16), Arc>>, + /// The mapping of program IDs to the latest edition. + editions: IndexMap, u16>, } impl Process { @@ -89,7 +91,8 @@ impl Process { let timer = timer!("Process:setup"); // Initialize the process. - let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new() }; + let mut process = + Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new(), editions: IndexMap::new() }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -97,7 +100,7 @@ impl Process { lap!(timer, "Load credits program"); // Compute the 'credits.aleo' program stack. - let stack = Stack::new(&process, &program)?; + let stack = Stack::new(&process, &program, 0)?; lap!(timer, "Initialize stack"); // Synthesize the 'credits.aleo' circuit keys. @@ -118,12 +121,12 @@ impl Process { /// Adds a new program to the process. /// If you intend to `execute` the program, use `deploy` and `finalize_deployment` instead. #[inline] - pub fn add_program(&mut self, program: &Program) -> Result<()> { + pub fn add_program(&mut self, program: &Program, edition: u16) -> Result<()> { // Initialize the 'credits.aleo' program ID. let credits_program_id = ProgramID::::from_str("credits.aleo")?; // If the program is not 'credits.aleo', compute the program stack, and add it to the process. if program.id() != &credits_program_id { - self.add_stack(Stack::new(self, program)?); + self.add_stack(Stack::new(self, program, edition)?); } Ok(()) } @@ -132,8 +135,13 @@ impl Process { /// If you intend to `execute` the program, use `deploy` and `finalize_deployment` instead. #[inline] pub fn add_stack(&mut self, stack: Stack) { + // Get the program ID. + let program_id = *stack.program_id(); + // Get the edition. + let edition = stack.edition(); // Add the stack to the process. - self.stacks.insert(*stack.program_id(), Arc::new(stack)); + self.stacks.insert((program_id, edition), Arc::new(stack)); + self.editions.insert(program_id, edition); } } @@ -144,7 +152,8 @@ impl Process { let timer = timer!("Process::load"); // Initialize the process. - let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new() }; + let mut process = + Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new(), editions: IndexMap::new() }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -152,7 +161,7 @@ impl Process { lap!(timer, "Load credits program"); // Compute the 'credits.aleo' program stack. - let stack = Stack::new(&process, &program)?; + let stack = Stack::new(&process, &program, 0)?; lap!(timer, "Initialize stack"); // Synthesize the 'credits.aleo' verifying keys. @@ -182,13 +191,14 @@ impl Process { #[cfg(feature = "wasm")] pub fn load_web() -> Result { // Initialize the process. - let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new() }; + let mut process = + Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new(), editions: IndexMap::new() }; // Initialize the 'credits.aleo' program. let program = Program::credits()?; // Compute the 'credits.aleo' program stack. - let stack = Stack::new(&process, &program)?; + let stack = Stack::new(&process, &program, 0)?; // Add the stack to the process. process.add_stack(stack); @@ -206,7 +216,7 @@ impl Process { /// Returns `true` if the process contains the program with the given ID. #[inline] pub fn contains_program(&self, program_id: &ProgramID) -> bool { - self.stacks.contains_key(program_id) + self.editions.contains_key(program_id) } /// Returns the stack for the given program ID. @@ -214,8 +224,12 @@ impl Process { pub fn get_stack(&self, program_id: impl TryInto>) -> Result<&Arc>> { // Prepare the program ID. let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?; + // Get the latest edition. + let edition = + *self.editions.get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; // Retrieve the stack. - let stack = self.stacks.get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; + let stack = + self.stacks.get(&(program_id, edition)).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; // Ensure the program ID matches. ensure!(stack.program_id() == &program_id, "Expected program '{}', found '{program_id}'", stack.program_id()); // Return the stack. @@ -318,7 +332,7 @@ pub mod test_helpers { // Add the program to the process if doesn't yet exist. if !process.contains_program(program.id()) { - process.add_program(program).unwrap(); + process.add_program(program, 0).unwrap(); } // Compute the authorization. @@ -452,7 +466,7 @@ function compute: // Construct a new process. let mut process = Process::load().unwrap(); // Add the program to the process. - process.add_program(program).unwrap(); + process.add_program(program, 0).unwrap(); // Return the process. process } diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 666c226cc5..cd6fa0d6d3 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -18,7 +18,7 @@ use super::*; impl Stack { /// Initializes a new stack, given the process and program. #[inline] - pub(crate) fn initialize(process: &Process, program: &Program) -> Result { + pub(crate) fn initialize(process: &Process, program: &Program, edition: u16) -> Result { // Construct the stack for the program. let mut stack = Self { program: program.clone(), @@ -32,6 +32,7 @@ impl Stack { finalize_costs: Default::default(), program_depth: 0, program_address: program.id().to_address()?, + edition, }; // Add all the imports into the stack. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 0c446a0795..283c15b8a3 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -195,12 +195,14 @@ pub struct Stack { program_depth: usize, /// The program address. program_address: Address, + /// The program edition. + edition: u16, } impl Stack { /// Initializes a new stack, if it does not already exist, given the process and the program. #[inline] - pub fn new(process: &Process, program: &Program) -> Result { + pub fn new(process: &Process, program: &Program, edition: u16) -> Result { // Retrieve the program ID. let program_id = program.id(); // Ensure the program does not already exist in the process. @@ -219,7 +221,7 @@ impl Stack { ensure!(program == &Program::from_str(&program_string)?, "Program string serialization failed"); // Return the stack. - Stack::initialize(process, program) + Stack::initialize(process, program, edition) } } @@ -248,6 +250,12 @@ impl StackProgram for Stack { &self.program_address } + /// Returns the program edition. + #[inline] + fn edition(&self) -> u16 { + self.edition + } + /// Returns `true` if the stack contains the external record. #[inline] fn contains_external_record(&self, locator: &Locator) -> bool { diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index 3af7055726..ad9daafa73 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -1103,7 +1103,7 @@ function transfer: assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program1).unwrap(); + process.add_program(&program1, 0).unwrap(); // Initialize the RNG. let rng = &mut TestRng::default(); @@ -1868,7 +1868,7 @@ function b: assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program1).unwrap(); + process.add_program(&program1, 0).unwrap(); // Initialize another program. let (string, program2) = Program::::parse( @@ -1887,7 +1887,7 @@ function a: assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program2).unwrap(); + process.add_program(&program2, 0).unwrap(); // Initialize the RNG. let rng = &mut TestRng::default(); @@ -2004,7 +2004,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program1).unwrap(); + process.add_program(&program1, 0).unwrap(); // Initialize another program. let (string, program2) = Program::::parse( @@ -2025,7 +2025,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program2).unwrap(); + process.add_program(&program2, 0).unwrap(); // Initialize another program. let (string, program3) = Program::::parse( @@ -2048,7 +2048,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program3).unwrap(); + process.add_program(&program3, 0).unwrap(); // Initialize another program. let (string, program4) = Program::::parse( @@ -2069,7 +2069,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program4).unwrap(); + process.add_program(&program4, 0).unwrap(); // Initialize the RNG. let rng = &mut TestRng::default(); @@ -2364,8 +2364,11 @@ fn test_process_deploy_credits_program() { let rng = &mut TestRng::default(); // Initialize an empty process without the `credits` program. - let empty_process = - Process { universal_srs: Arc::new(UniversalSRS::::load().unwrap()), stacks: IndexMap::new() }; + let empty_process = Process { + universal_srs: Arc::new(UniversalSRS::::load().unwrap()), + stacks: IndexMap::new(), + editions: IndexMap::new(), + }; // Construct the process. let process = Process::load().unwrap(); @@ -2513,7 +2516,7 @@ fn test_long_import_chain() { )) .unwrap(); // Add the program to the process. - process.add_program(&program).unwrap(); + process.add_program(&program, 0).unwrap(); } // Add the `MAX_PROGRAM_DEPTH + 1` program to the process, which should fail. @@ -2526,7 +2529,7 @@ fn test_long_import_chain() { CurrentNetwork::MAX_PROGRAM_DEPTH + 1 )) .unwrap(); - let result = process.add_program(&program); + let result = process.add_program(&program, 0); assert!(result.is_err()); } @@ -2559,7 +2562,7 @@ fn test_long_import_chain_with_calls() { )) .unwrap(); // Add the program to the process. - process.add_program(&program).unwrap(); + process.add_program(&program, 0).unwrap(); // Check that the number of calls is correct. let stack = process.get_stack(program.id()).unwrap(); let number_of_calls = stack.get_number_of_calls(program.functions().into_iter().next().unwrap().0).unwrap(); @@ -2578,7 +2581,7 @@ fn test_long_import_chain_with_calls() { Transaction::::MAX_TRANSITIONS - 2 )) .unwrap(); - let result = process.add_program(&program); + let result = process.add_program(&program, 0); assert!(result.is_err()) } @@ -2593,7 +2596,7 @@ fn test_max_imports() { // Initialize a new program. let program = Program::from_str(&format!("program test{i}.aleo; function c:")).unwrap(); // Add the program to the process. - process.add_program(&program).unwrap(); + process.add_program(&program, 0).unwrap(); } // Add a program importing all `MAX_IMPORTS` programs, which should pass. @@ -2602,7 +2605,7 @@ fn test_max_imports() { let program = Program::from_str(&format!("{import_string}program test{}.aleo; function c:", CurrentNetwork::MAX_IMPORTS)) .unwrap(); - process.add_program(&program).unwrap(); + process.add_program(&program, 0).unwrap(); // Attempt to construct a program importing `MAX_IMPORTS + 1` programs, which should fail. let import_string = @@ -2639,10 +2642,10 @@ fn test_program_exceeding_transaction_spend_limit() { let mut process = Process::::load().unwrap(); // Attempt to add the program to the process, which should fail. - let result = process.add_program(&program); + let result = process.add_program(&program, 0); assert!(result.is_err()); // Attempt to initialize a `Stack` directly with the program, which should fail. - let result = Stack::initialize(&process, &program); + let result = Stack::initialize(&process, &program, 0); assert!(result.is_err()); } diff --git a/synthesizer/process/src/verify_deployment.rs b/synthesizer/process/src/verify_deployment.rs index 3aca6313ab..b14a41122d 100644 --- a/synthesizer/process/src/verify_deployment.rs +++ b/synthesizer/process/src/verify_deployment.rs @@ -31,7 +31,7 @@ impl Process { ensure!(!self.contains_program(program_id), "Program '{program_id}' already exists"); // Ensure the program is well-formed, by computing the stack. - let stack = Stack::new(self, deployment.program())?; + let stack = Stack::new(self, deployment.program(), deployment.edition())?; lap!(timer, "Compute the stack"); // Ensure the verifying keys are well-formed and the certificates are valid. diff --git a/synthesizer/program/src/logic/instruction/operation/async_.rs b/synthesizer/program/src/logic/instruction/operation/async_.rs index b151dd7313..ac8301a390 100644 --- a/synthesizer/program/src/logic/instruction/operation/async_.rs +++ b/synthesizer/program/src/logic/instruction/operation/async_.rs @@ -391,7 +391,7 @@ mod tests { // let operands = vec![operand_a, operand_b]; // // // Initialize the stack. - // let stack = Stack::new(&Process::load()?, &program)?; + // let stack = Stack::new(&Process::load()?, &program, 0)?; // // Ok((stack, operands)) // } diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index 73a7859675..07c7bfe08d 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -72,6 +72,9 @@ pub trait StackProgram { /// Returns the program address. fn program_address(&self) -> &Address; + /// Returns the program edition. + fn edition(&self) -> u16; + /// Returns `true` if the stack contains the external record. fn contains_external_record(&self, locator: &Locator) -> bool; diff --git a/synthesizer/program/tests/instruction/assert.rs b/synthesizer/program/tests/instruction/assert.rs index 6c06918b23..519fa838f7 100644 --- a/synthesizer/program/tests/instruction/assert.rs +++ b/synthesizer/program/tests/instruction/assert.rs @@ -72,7 +72,7 @@ fn sample_stack( let operands = vec![operand_a, operand_b]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program)?; + let stack = Stack::new(&Process::load()?, &program, 0)?; Ok((stack, operands)) } diff --git a/synthesizer/program/tests/instruction/commit.rs b/synthesizer/program/tests/instruction/commit.rs index c22f362f11..37bbee2787 100644 --- a/synthesizer/program/tests/instruction/commit.rs +++ b/synthesizer/program/tests/instruction/commit.rs @@ -93,7 +93,7 @@ fn sample_stack( let operands = vec![operand_a, operand_b]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program)?; + let stack = Stack::new(&Process::load()?, &program, 0)?; Ok((stack, operands, r2)) } diff --git a/synthesizer/program/tests/instruction/hash.rs b/synthesizer/program/tests/instruction/hash.rs index 106474e978..3f4e185a9e 100644 --- a/synthesizer/program/tests/instruction/hash.rs +++ b/synthesizer/program/tests/instruction/hash.rs @@ -109,7 +109,7 @@ fn sample_stack( let operands = vec![Operand::Register(r0)]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program)?; + let stack = Stack::new(&Process::load()?, &program, 0)?; Ok((stack, operands, r1)) } diff --git a/synthesizer/program/tests/instruction/is.rs b/synthesizer/program/tests/instruction/is.rs index 1d58f84cdd..51f2e84363 100644 --- a/synthesizer/program/tests/instruction/is.rs +++ b/synthesizer/program/tests/instruction/is.rs @@ -83,7 +83,7 @@ fn sample_stack( let operands = vec![operand_a, operand_b]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program)?; + let stack = Stack::new(&Process::load()?, &program, 0)?; Ok((stack, operands, r2)) } diff --git a/synthesizer/tests/test_process_execute.rs b/synthesizer/tests/test_process_execute.rs index 8d63e707fd..caaedbfcec 100644 --- a/synthesizer/tests/test_process_execute.rs +++ b/synthesizer/tests/test_process_execute.rs @@ -57,7 +57,7 @@ fn run_test(process: Process, test: &ProgramTest) -> serde_yaml: // Add the programs into the process. let mut process = process.clone(); for program in test.programs() { - if let Err(err) = process.add_program(program) { + if let Err(err) = process.add_program(program, 0) { output .get_mut(serde_yaml::Value::String("errors".to_string())) .unwrap() diff --git a/vm/file/prover.rs b/vm/file/prover.rs index 1bac807b46..864cbe37b1 100644 --- a/vm/file/prover.rs +++ b/vm/file/prover.rs @@ -230,7 +230,7 @@ function compute: // Construct the process. let mut process = Process::load().unwrap(); // Add the program to the process. - process.add_program(&program).unwrap(); + process.add_program(&program, 0).unwrap(); // Prepare the function name. let function_name = Identifier::from_str("compute").unwrap(); diff --git a/vm/file/verifier.rs b/vm/file/verifier.rs index af0b4f9673..b55a59f54d 100644 --- a/vm/file/verifier.rs +++ b/vm/file/verifier.rs @@ -230,7 +230,7 @@ function compute: // Construct the process. let mut process = Process::load().unwrap(); // Add the program to the process. - process.add_program(&program).unwrap(); + process.add_program(&program, 0).unwrap(); // Prepare the function name. let function_name = Identifier::from_str("compute").unwrap(); diff --git a/vm/package/deploy.rs b/vm/package/deploy.rs index f423ed51de..bebc12b7cf 100644 --- a/vm/package/deploy.rs +++ b/vm/package/deploy.rs @@ -137,7 +137,7 @@ impl Package { // Open the Aleo program file. let import_program_file = AleoFile::open(&imports_directory, program_id, false)?; // Add the import program. - process.add_program(import_program_file.program())?; + process.add_program(import_program_file.program(), 0)?; Ok::<_, Error>(()) })?; diff --git a/vm/package/mod.rs b/vm/package/mod.rs index 678d892400..55b7752f95 100644 --- a/vm/package/mod.rs +++ b/vm/package/mod.rs @@ -166,13 +166,13 @@ impl Package { // Open the Aleo program file. let import_program_file = AleoFile::open(&imports_directory, program_id, false)?; // Add the import program. - process.add_program(import_program_file.program())?; + process.add_program(import_program_file.program(), 0)?; } Ok::<_, Error>(()) })?; // Add the program to the process. - process.add_program(self.program())?; + process.add_program(self.program(), 0)?; Ok(process) } From aff18e87351faa2b0e186997839566978d8fb497 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Mon, 27 Jan 2025 08:55:45 -0800 Subject: [PATCH 09/46] Checkpoint --- console/program/src/owner/mod.rs | 2 +- synthesizer/process/src/lib.rs | 31 +++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/console/program/src/owner/mod.rs b/console/program/src/owner/mod.rs index 97e0a2f259..fc14eff016 100644 --- a/console/program/src/owner/mod.rs +++ b/console/program/src/owner/mod.rs @@ -159,7 +159,7 @@ pub struct ProgramOwnerV2 { /// The address of the program owner. address: Address, /// The address of the authority allowed to update the program. - authority: Address, + authority: Option>, /// The edition of the program. edition: U16, /// The signature of the program owner, over the deployment transaction ID. diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index d0356d1d69..9915115ec5 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -81,7 +81,7 @@ pub struct Process { /// The mapping of program IDs to stacks. stacks: IndexMap<(ProgramID, u16), Arc>>, /// The mapping of program IDs to the latest edition. - editions: IndexMap, u16>, + editions: Arc, u16>>>, } impl Process { @@ -91,8 +91,11 @@ impl Process { let timer = timer!("Process:setup"); // Initialize the process. - let mut process = - Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new(), editions: IndexMap::new() }; + let mut process = Self { + universal_srs: Arc::new(UniversalSRS::load()?), + stacks: IndexMap::new(), + editions: Arc::new(RwLock::new(IndexMap::new())), + }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -139,9 +142,11 @@ impl Process { let program_id = *stack.program_id(); // Get the edition. let edition = stack.edition(); + // Acquire the write lock. + let mut editions = self.editions.write(); // Add the stack to the process. self.stacks.insert((program_id, edition), Arc::new(stack)); - self.editions.insert(program_id, edition); + editions.insert(program_id, edition); } } @@ -152,8 +157,11 @@ impl Process { let timer = timer!("Process::load"); // Initialize the process. - let mut process = - Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new(), editions: IndexMap::new() }; + let mut process = Self { + universal_srs: Arc::new(UniversalSRS::load()?), + stacks: IndexMap::new(), + editions: Arc::new(RwLock::new(IndexMap::new())), + }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -191,8 +199,11 @@ impl Process { #[cfg(feature = "wasm")] pub fn load_web() -> Result { // Initialize the process. - let mut process = - Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new(), editions: IndexMap::new() }; + let mut process = Self { + universal_srs: Arc::new(UniversalSRS::load()?), + stacks: IndexMap::new(), + editions: Arc::new(RwLock::new(IndexMap::new())), + }; // Initialize the 'credits.aleo' program. let program = Program::credits()?; @@ -216,7 +227,7 @@ impl Process { /// Returns `true` if the process contains the program with the given ID. #[inline] pub fn contains_program(&self, program_id: &ProgramID) -> bool { - self.editions.contains_key(program_id) + self.editions.read().contains_key(program_id) } /// Returns the stack for the given program ID. @@ -226,7 +237,7 @@ impl Process { let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?; // Get the latest edition. let edition = - *self.editions.get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; + *self.editions.read().get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; // Retrieve the stack. let stack = self.stacks.get(&(program_id, edition)).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; From 6b7286e5d02fcad2814cc3ed019a210cff25bbf0 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 30 Jan 2025 17:24:40 -0800 Subject: [PATCH 10/46] Lookup stacks in Process, remove external stacks --- console/program/src/owner/mod.rs | 2 +- synthesizer/process/src/cost.rs | 2 +- synthesizer/process/src/deploy.rs | 2 +- synthesizer/process/src/finalize.rs | 93 ++++++++++--------- synthesizer/process/src/lib.rs | 51 ++++------ synthesizer/process/src/stack/call/mod.rs | 28 ++++-- .../src/stack/finalize_types/initialize.rs | 9 +- .../process/src/stack/finalize_types/mod.rs | 56 +++++------ .../process/src/stack/helpers/initialize.rs | 21 +---- .../process/src/stack/helpers/matches.rs | 15 ++- .../process/src/stack/helpers/sample.rs | 11 ++- synthesizer/process/src/stack/mod.rs | 47 ++-------- .../src/stack/register_types/initialize.rs | 15 ++- .../process/src/stack/register_types/mod.rs | 69 +++++++------- synthesizer/process/src/tests/test_execute.rs | 19 ++-- .../src/logic/instruction/operation/call.rs | 20 ++-- .../program/src/traits/stack_and_registers.rs | 12 +-- synthesizer/src/vm/finalize.rs | 8 +- 18 files changed, 223 insertions(+), 257 deletions(-) diff --git a/console/program/src/owner/mod.rs b/console/program/src/owner/mod.rs index fc14eff016..97e0a2f259 100644 --- a/console/program/src/owner/mod.rs +++ b/console/program/src/owner/mod.rs @@ -159,7 +159,7 @@ pub struct ProgramOwnerV2 { /// The address of the program owner. address: Address, /// The address of the authority allowed to update the program. - authority: Option>, + authority: Address, /// The edition of the program. edition: U16, /// The signature of the program owner, over the deployment transaction ID. diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 4f75e9d2c7..5d805d0989 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -446,7 +446,7 @@ pub fn cost_in_microcredits_v1(stack: &Stack, function_name: &Ide let stack = stack.get_external_stack(future.program_id())?; // Accumulate the finalize cost of the future. future_cost = future_cost - .checked_add(cost_in_microcredits_v1(stack, future.resource())?) + .checked_add(cost_in_microcredits_v1(&stack, future.resource())?) .ok_or(anyhow!("Finalize cost overflowed"))?; } } diff --git a/synthesizer/process/src/deploy.rs b/synthesizer/process/src/deploy.rs index e9bd276624..e8c10aef37 100644 --- a/synthesizer/process/src/deploy.rs +++ b/synthesizer/process/src/deploy.rs @@ -56,7 +56,7 @@ impl Process { lap!(timer, "Insert the verifying keys"); // Add the stack to the process. - self.add_stack(stack); + self.add_stack(stack)?; finish!(timer); diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index f78ae50733..4d977db6d1 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -222,7 +222,7 @@ impl Process { fn finalize_fee_transition>( state: FinalizeGlobalState, store: &FinalizeStore, - stack: &Stack, + stack: &Arc>, fee: &Fee, ) -> Result>> { // Construct the call graph. @@ -245,7 +245,7 @@ fn finalize_fee_transition>( fn finalize_transition>( state: FinalizeGlobalState, store: &FinalizeStore, - stack: &Stack, + stack: &Arc>, transition: &Transition, call_graph: HashMap>, ) -> Result>> { @@ -284,15 +284,18 @@ fn finalize_transition>( states.push(initialize_finalize_state(state, future, stack, *transition.id(), nonce)?); // While there are active finalize states, finalize them. - 'outer: while let Some(FinalizeState { - mut counter, - finalize, - mut registers, - stack, - mut call_counter, - mut awaited, - }) = states.pop() + 'outer: while let Some(FinalizeState { mut counter, mut registers, stack, mut call_counter, mut awaited }) = + states.pop() { + // Get the finalize logic. + let finalize = match stack.get_function_ref(future.function_name())?.finalize_logic() { + Some(finalize) => finalize, + None => bail!( + "The function '{}/{}' does not have an associated finalize block", + future.program_id(), + future.function_name() + ), + }; // Evaluate the commands. while counter < finalize.commands().len() { // Retrieve the command. @@ -300,7 +303,7 @@ fn finalize_transition>( // Finalize the command. match &command { Command::BranchEq(branch_eq) => { - let result = try_vm_runtime!(|| branch_to(counter, branch_eq, finalize, stack, ®isters)); + let result = try_vm_runtime!(|| branch_to(counter, branch_eq, finalize, stack.deref(), ®isters)); match result { Ok(Ok(new_counter)) => { counter = new_counter; @@ -312,7 +315,8 @@ fn finalize_transition>( } } Command::BranchNeq(branch_neq) => { - let result = try_vm_runtime!(|| branch_to(counter, branch_neq, finalize, stack, ®isters)); + let result = + try_vm_runtime!(|| branch_to(counter, branch_neq, finalize, stack.deref(), ®isters)); match result { Ok(Ok(new_counter)) => { counter = new_counter; @@ -358,14 +362,20 @@ fn finalize_transition>( nonce += 1; // Set up the finalize state for the await. - let callee_state = - match try_vm_runtime!(|| setup_await(state, await_, stack, ®isters, transition_id, nonce)) { - Ok(Ok(callee_state)) => callee_state, - // If the evaluation fails, bail and return the error. - Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"), - // If the evaluation fails, bail and return the error. - Err(_) => bail!("'finalize' failed to evaluate command ({command})"), - }; + let callee_state = match try_vm_runtime!(|| setup_await( + state, + await_, + &stack, + ®isters, + transition_id, + nonce + )) { + Ok(Ok(callee_state)) => callee_state, + // If the evaluation fails, bail and return the error. + Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"), + // If the evaluation fails, bail and return the error. + Err(_) => bail!("'finalize' failed to evaluate command ({command})"), + }; // Increment the call counter. call_counter += 1; @@ -375,7 +385,7 @@ fn finalize_transition>( awaited.insert(await_.register().clone()); // Aggregate the caller state. - let caller_state = FinalizeState { counter, finalize, registers, stack, call_counter, awaited }; + let caller_state = FinalizeState { counter, registers, stack, call_counter, awaited }; // Push the caller state onto the stack. states.push(caller_state); @@ -385,7 +395,7 @@ fn finalize_transition>( continue 'outer; } _ => { - let result = try_vm_runtime!(|| command.finalize(stack, store, &mut registers)); + let result = try_vm_runtime!(|| command.finalize(stack.deref(), store, &mut registers)); match result { // If the evaluation succeeds with an operation, add it to the list. Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation), @@ -419,15 +429,13 @@ fn finalize_transition>( } // A helper struct to track the execution of a finalize block. -struct FinalizeState<'a, N: Network> { +struct FinalizeState { // A counter for the index of the commands. counter: usize, - // The finalize logic. - finalize: &'a Finalize, // The registers. registers: FinalizeRegisters, // The stack. - stack: &'a Stack, + stack: Arc>, // Call counter. call_counter: usize, // Awaited futures. @@ -435,23 +443,20 @@ struct FinalizeState<'a, N: Network> { } // A helper function to initialize the finalize state. -fn initialize_finalize_state<'a, N: Network>( +fn initialize_finalize_state( state: FinalizeGlobalState, future: &Future, - stack: &'a Stack, + stack: &Arc>, transition_id: N::TransitionID, nonce: u64, -) -> Result> { - // Get the finalize logic and the stack. - let (finalize, stack) = match stack.program_id() == future.program_id() { - true => (stack.get_function_ref(future.function_name())?.finalize_logic(), stack), - false => { - let stack = stack.get_external_stack(future.program_id())?.as_ref(); - (stack.get_function_ref(future.function_name())?.finalize_logic(), stack) - } +) -> Result> { + // Get the stack. + let stack = match stack.get_external_stack(future.program_id()) { + Ok(stack) => stack, + Err(_) => stack.clone(), }; - // Check that the finalize logic exists. - let finalize = match finalize { + // Get the finalize logic and check that it exists. + let finalize = match stack.get_function_ref(future.function_name())?.finalize_logic() { Some(finalize) => finalize, None => bail!( "The function '{}/{}' does not have an associated finalize block", @@ -472,25 +477,25 @@ fn initialize_finalize_state<'a, N: Network>( finalize.inputs().iter().map(|i| i.register()).zip_eq(future.arguments().iter()).try_for_each( |(register, input)| { // Assign the input value to the register. - registers.store(stack, register, Value::from(input)) + registers.store(stack.deref(), register, Value::from(input)) }, )?; - Ok(FinalizeState { counter: 0, finalize, registers, stack, call_counter: 0, awaited: Default::default() }) + Ok(FinalizeState { counter: 0, registers, stack, call_counter: 0, awaited: Default::default() }) } // A helper function that sets up the await operation. #[inline] -fn setup_await<'a, N: Network>( +fn setup_await( state: FinalizeGlobalState, await_: &Await, - stack: &'a Stack, + stack: &Arc>, registers: &FinalizeRegisters, transition_id: N::TransitionID, nonce: u64, -) -> Result> { +) -> Result> { // Retrieve the input as a future. - let future = match registers.load(stack, &Operand::Register(await_.register().clone()))? { + let future = match registers.load(stack.deref(), &Operand::Register(await_.register().clone()))? { Value::Future(future) => future, _ => bail!("The input to 'await' is not a future"), }; diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index 9915115ec5..1efd72d765 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -79,9 +79,7 @@ pub struct Process { /// The universal SRS. universal_srs: Arc>, /// The mapping of program IDs to stacks. - stacks: IndexMap<(ProgramID, u16), Arc>>, - /// The mapping of program IDs to the latest edition. - editions: Arc, u16>>>, + stacks: Arc, Arc>>>, } impl Process { @@ -91,11 +89,7 @@ impl Process { let timer = timer!("Process:setup"); // Initialize the process. - let mut process = Self { - universal_srs: Arc::new(UniversalSRS::load()?), - stacks: IndexMap::new(), - editions: Arc::new(RwLock::new(IndexMap::new())), - }; + let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(IndexMap::new()) }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -114,7 +108,7 @@ impl Process { lap!(timer, "Synthesize credits program keys"); // Add the 'credits.aleo' stack to the process. - process.add_stack(stack); + process.add_stack(stack)?; finish!(timer); // Return the process. @@ -129,7 +123,7 @@ impl Process { let credits_program_id = ProgramID::::from_str("credits.aleo")?; // If the program is not 'credits.aleo', compute the program stack, and add it to the process. if program.id() != &credits_program_id { - self.add_stack(Stack::new(self, program, edition)?); + self.add_stack(Stack::new(self, program, edition)?)?; } Ok(()) } @@ -137,16 +131,15 @@ impl Process { /// Adds a new stack to the process. /// If you intend to `execute` the program, use `deploy` and `finalize_deployment` instead. #[inline] - pub fn add_stack(&mut self, stack: Stack) { + pub fn add_stack(&mut self, stack: Stack) -> Result<()> { // Get the program ID. let program_id = *stack.program_id(); - // Get the edition. - let edition = stack.edition(); - // Acquire the write lock. - let mut editions = self.editions.write(); // Add the stack to the process. - self.stacks.insert((program_id, edition), Arc::new(stack)); - editions.insert(program_id, edition); + Arc::get_mut(&mut self.stacks) + .ok_or_else(|| anyhow!("Failed to add stack"))? + .insert(program_id, Arc::new(stack)); + + Ok(()) } } @@ -157,11 +150,7 @@ impl Process { let timer = timer!("Process::load"); // Initialize the process. - let mut process = Self { - universal_srs: Arc::new(UniversalSRS::load()?), - stacks: IndexMap::new(), - editions: Arc::new(RwLock::new(IndexMap::new())), - }; + let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(IndexMap::new()) }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -187,7 +176,7 @@ impl Process { lap!(timer, "Load circuit keys"); // Add the stack to the process. - process.add_stack(stack); + process.add_stack(stack)?; finish!(timer, "Process::load"); // Return the process. @@ -199,11 +188,7 @@ impl Process { #[cfg(feature = "wasm")] pub fn load_web() -> Result { // Initialize the process. - let mut process = Self { - universal_srs: Arc::new(UniversalSRS::load()?), - stacks: IndexMap::new(), - editions: Arc::new(RwLock::new(IndexMap::new())), - }; + let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(IndexMap::new()) }; // Initialize the 'credits.aleo' program. let program = Program::credits()?; @@ -212,7 +197,7 @@ impl Process { let stack = Stack::new(&process, &program, 0)?; // Add the stack to the process. - process.add_stack(stack); + process.add_stack(stack)?; // Return the process. Ok(process) @@ -227,7 +212,7 @@ impl Process { /// Returns `true` if the process contains the program with the given ID. #[inline] pub fn contains_program(&self, program_id: &ProgramID) -> bool { - self.editions.read().contains_key(program_id) + self.stacks.contains_key(program_id) } /// Returns the stack for the given program ID. @@ -235,12 +220,8 @@ impl Process { pub fn get_stack(&self, program_id: impl TryInto>) -> Result<&Arc>> { // Prepare the program ID. let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?; - // Get the latest edition. - let edition = - *self.editions.read().get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; // Retrieve the stack. - let stack = - self.stacks.get(&(program_id, edition)).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; + let stack = self.stacks.get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; // Ensure the program ID matches. ensure!(stack.program_id() == &program_id, "Expected program '{}', found '{program_id}'", stack.program_id()); // Return the stack. diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index d5f1a805c9..b4d1f63a53 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -70,11 +70,11 @@ impl CallTrait for Call { // Load the operands values. let inputs: Vec<_> = self.operands().iter().map(|operand| registers.load(stack, operand)).try_collect()?; - // Retrieve the substack and resource. - let (substack, resource) = match self.operator() { + // Retrieve the optional external stack and resource. + let (external_stack, resource) = match self.operator() { // Retrieve the call stack and resource from the locator. CallOperator::Locator(locator) => { - (stack.get_external_stack(locator.program_id())?.as_ref(), locator.resource()) + (Some(stack.get_external_stack(locator.program_id())?), locator.resource()) } CallOperator::Resource(resource) => { // TODO (howardwu): Revisit this decision to forbid calling internal functions. A record cannot be spent again. @@ -83,10 +83,14 @@ impl CallTrait for Call { if stack.program().contains_function(resource) { bail!("Cannot call '{resource}'. Use a closure ('closure {resource}:') instead.") } - - (stack, resource) + (None, resource) } }; + // Retrieve the substack. + let substack = match &external_stack { + Some(external_stack) => external_stack.as_ref(), + None => stack, + }; lap!(timer, "Retrieved the substack and resource"); // If the operator is a closure, retrieve the closure and compute the output. @@ -154,8 +158,8 @@ impl CallTrait for Call { let inputs: Vec<_> = self.operands().iter().map(|operand| registers.load_circuit(stack, operand)).try_collect()?; - // Retrieve the substack and resource. - let (substack, resource) = match self.operator() { + // Retrieve the optional external stack and resource. + let (external_stack, resource) = match self.operator() { // Retrieve the call stack and resource from the locator. CallOperator::Locator(locator) => { // Check the external call locator. @@ -168,7 +172,7 @@ impl CallTrait for Call { if is_credits_program && (is_fee_private || is_fee_public) { bail!("Cannot perform an external call to 'credits.aleo/fee_private' or 'credits.aleo/fee_public'.") } else { - (stack.get_external_stack(locator.program_id())?.as_ref(), locator.resource()) + (Some(stack.get_external_stack(locator.program_id())?), locator.resource()) } } CallOperator::Resource(resource) => { @@ -178,10 +182,14 @@ impl CallTrait for Call { if stack.program().contains_function(resource) { bail!("Cannot call '{resource}'. Use a closure ('closure {resource}:') instead.") } - - (stack, resource) + (None, resource) } }; + // Retrieve the substack. + let substack = match &external_stack { + Some(external_stack) => external_stack.as_ref(), + None => stack, + }; lap!(timer, "Retrieve the substack and resource"); // If we are not handling the root request, retrieve the root request's tvk diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index dfb9ad2dc1..80f5d7db52 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -266,7 +266,8 @@ impl FinalizeTypes { bail!("External program '{program_id}' is not imported by '{}'.", stack.program_id()); } // Retrieve the program. - let external = stack.get_external_program(program_id)?; + let external_stack = stack.get_external_stack(program_id)?; + let external = external_stack.program(); // Ensure the mapping exists in the program. if !external.contains_mapping(mapping_name) { bail!("Mapping '{mapping_name}' in '{program_id}' is not defined.") @@ -328,7 +329,8 @@ impl FinalizeTypes { bail!("External program '{program_id}' is not imported by '{}'.", stack.program_id()); } // Retrieve the program. - let external = stack.get_external_program(program_id)?; + let external_stack = stack.get_external_stack(program_id)?; + let external = external_stack.program(); // Ensure the mapping exists in the program. if !external.contains_mapping(mapping_name) { bail!("Mapping '{mapping_name}' in '{program_id}' is not defined.") @@ -394,7 +396,8 @@ impl FinalizeTypes { bail!("External program '{locator}' is not imported by '{program_id}'."); } // Retrieve the program. - let external = stack.get_external_program(program_id)?; + let external_stack = stack.get_external_stack(program_id)?; + let external = external_stack.program(); // Ensure the mapping exists in the program. if !external.contains_mapping(mapping_name) { bail!("Mapping '{mapping_name}' in '{program_id}' is not defined.") diff --git a/synthesizer/process/src/stack/finalize_types/mod.rs b/synthesizer/process/src/stack/finalize_types/mod.rs index d2563f4a71..d88db847cd 100644 --- a/synthesizer/process/src/stack/finalize_types/mod.rs +++ b/synthesizer/process/src/stack/finalize_types/mod.rs @@ -26,7 +26,6 @@ use console::{ FinalizeType, Identifier, LiteralType, - Locator, PlaintextType, Register, RegisterType, @@ -123,14 +122,6 @@ impl FinalizeTypes { self.destinations.get(®ister.locator()).ok_or_else(|| anyhow!("Register '{register}' does not exist"))? }; - // A helper enum to track the type of the register. - enum FinalizeRefType<'a, N: Network> { - /// A plaintext type. - Plaintext(&'a PlaintextType), - /// A finalize type. - Future(&'a Locator), - } - // Retrieve the path if the register is an access. Otherwise, return the type. let (mut finalize_type, path) = match (finalize_type, register) { // If the register is a locator, then output the register type. @@ -140,10 +131,7 @@ impl FinalizeTypes { // Ensure the path is valid. ensure!(!path.is_empty(), "Register '{register}' references no accesses."); // Return the finalize type and path. - match finalize_type { - FinalizeType::Plaintext(plaintext_type) => (FinalizeRefType::Plaintext(plaintext_type), path), - FinalizeType::Future(locator) => (FinalizeRefType::Future(locator), path), - } + (finalize_type.clone(), path) } }; @@ -151,36 +139,39 @@ impl FinalizeTypes { for access in path.iter() { match (&finalize_type, access) { // Ensure the plaintext type is not a literal, as the register references an access. - (FinalizeRefType::Plaintext(PlaintextType::Literal(..)), _) => { + (FinalizeType::Plaintext(PlaintextType::Literal(..)), _) => { bail!("'{register}' references a literal.") } // Access the member on the path to output the register type. - (FinalizeRefType::Plaintext(PlaintextType::Struct(struct_name)), Access::Member(identifier)) => { + (FinalizeType::Plaintext(PlaintextType::Struct(struct_name)), Access::Member(identifier)) => { // Retrieve the member type from the struct and check that it exists. match stack.program().get_struct(struct_name)?.members().get(identifier) { // Retrieve the member and update `finalize_type` for the next iteration. - Some(member_type) => finalize_type = FinalizeRefType::Plaintext(member_type), + Some(member_type) => finalize_type = FinalizeType::Plaintext(member_type.clone()), // Halts if the member does not exist. None => bail!("'{identifier}' does not exist in struct '{struct_name}'"), } } // Access the member on the path to output the register type and check that it is in bounds. - (FinalizeRefType::Plaintext(PlaintextType::Array(array_type)), Access::Index(index)) => { + (FinalizeType::Plaintext(PlaintextType::Array(array_type)), Access::Index(index)) => { match index < array_type.length() { // Retrieve the element type and update `finalize_type` for the next iteration. - true => finalize_type = FinalizeRefType::Plaintext(array_type.next_element_type()), + true => finalize_type = FinalizeType::Plaintext(array_type.next_element_type().clone()), // Halts if the index is out of bounds. false => bail!("Index out of bounds"), } } // Access the input to the future to output the register type and check that it is in bounds. - (FinalizeRefType::Future(locator), Access::Index(index)) => { + (FinalizeType::Future(locator), Access::Index(index)) => { + // Get the external stack, if needed. + let external_stack = match locator.program_id() == stack.program_id() { + true => None, + false => Some(stack.get_external_stack(locator.program_id())?), + }; // Retrieve the associated function. - let function = match locator.program_id() == stack.program_id() { - true => stack.get_function_ref(locator.resource())?, - false => { - stack.get_external_program(locator.program_id())?.get_function_ref(locator.resource())? - } + let function = match &external_stack { + Some(external_stack) => external_stack.get_function_ref(locator.resource())?, + None => stack.get_function_ref(locator.resource())?, }; // Retrieve the finalize inputs. let finalize_inputs = match function.finalize_logic() { @@ -192,26 +183,25 @@ impl FinalizeTypes { // Retrieve the input type and update `finalize_type` for the next iteration. Some(input) => { finalize_type = match input.finalize_type() { - FinalizeType::Plaintext(plaintext_type) => FinalizeRefType::Plaintext(plaintext_type), - FinalizeType::Future(locator) => FinalizeRefType::Future(locator), + FinalizeType::Plaintext(plaintext_type) => { + FinalizeType::Plaintext(plaintext_type.clone()) + } + FinalizeType::Future(locator) => FinalizeType::Future(*locator), } } // Halts if the index is out of bounds. None => bail!("Index out of bounds"), } } - (FinalizeRefType::Plaintext(PlaintextType::Struct(..)), Access::Index(..)) - | (FinalizeRefType::Plaintext(PlaintextType::Array(..)), Access::Member(..)) - | (FinalizeRefType::Future(..), Access::Member(..)) => { + (FinalizeType::Plaintext(PlaintextType::Struct(..)), Access::Index(..)) + | (FinalizeType::Plaintext(PlaintextType::Array(..)), Access::Member(..)) + | (FinalizeType::Future(..), Access::Member(..)) => { bail!("Invalid access `{access}`") } } } // Return the output type. - Ok(match finalize_type { - FinalizeRefType::Plaintext(plaintext_type) => FinalizeType::Plaintext(plaintext_type.clone()), - FinalizeRefType::Future(locator) => FinalizeType::Future(*locator), - }) + Ok(finalize_type) } } diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index cd6fa0d6d3..39a1e2511b 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -22,7 +22,7 @@ impl Stack { // Construct the stack for the program. let mut stack = Self { program: program.clone(), - external_stacks: Default::default(), + stacks: Arc::downgrade(&process.stacks), register_types: Default::default(), finalize_types: Default::default(), universal_srs: process.universal_srs().clone(), @@ -44,7 +44,7 @@ impl Stack { // Retrieve the external stack for the import program ID. let external_stack = process.get_stack(import)?; // Add the external stack to the stack. - stack.insert_external_stack(external_stack.clone())?; + // TODO (@d0cd): Handle bookkeeping here. // Update the program depth, checking that it does not exceed the maximum call depth. stack.program_depth = std::cmp::max(stack.program_depth, external_stack.program_depth() + 1); ensure!( @@ -105,23 +105,6 @@ impl Stack { } impl Stack { - /// Inserts the given external stack to the stack. - #[inline] - fn insert_external_stack(&mut self, external_stack: Arc>) -> Result<()> { - // Retrieve the program ID. - let program_id = *external_stack.program_id(); - // Ensure the external stack is not already added. - ensure!(!self.external_stacks.contains_key(&program_id), "Program '{program_id}' already exists"); - // Ensure the program exists in the main program imports. - ensure!(self.program.contains_import(&program_id), "'{program_id}' does not exist in the main program imports"); - // Ensure the external stack is not for the main program. - ensure!(self.program.id() != external_stack.program_id(), "External stack program cannot be the main program"); - // Add the external stack to the stack. - self.external_stacks.insert(program_id, external_stack); - // Return success. - Ok(()) - } - /// Inserts the given closure to the stack. #[inline] fn insert_closure(&mut self, closure: &Closure) -> Result<()> { diff --git a/synthesizer/process/src/stack/helpers/matches.rs b/synthesizer/process/src/stack/helpers/matches.rs index d1aa51322e..967c5202bf 100644 --- a/synthesizer/process/src/stack/helpers/matches.rs +++ b/synthesizer/process/src/stack/helpers/matches.rs @@ -57,8 +57,10 @@ impl StackMatches for Stack { // Ensure the record name is valid. ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved"); + // Retrieve the external stack. + let external_stack = self.get_external_stack(locator.program_id())?; // Retrieve the record type from the program. - let Ok(record_type) = self.get_external_record(locator) else { + let Ok(record_type) = external_stack.program().get_record(locator.resource()) else { bail!("External '{locator}' is not defined in the program") }; @@ -296,10 +298,15 @@ impl Stack { // Ensure that the function names match. ensure!(future.function_name() == locator.resource(), "Future name does not match"); + // Retrieve the external stack, if needed. + let external_stack = match locator.program_id() == self.program_id() { + true => None, + false => Some(self.get_external_stack(locator.program_id())?), + }; // Retrieve the associated function. - let function = match locator.program_id() == self.program_id() { - true => self.get_function_ref(locator.resource())?, - false => self.get_external_program(locator.program_id())?.get_function_ref(locator.resource())?, + let function = match external_stack { + Some(external_stack) => external_stack.get_function(locator.resource())?, + None => self.get_function(locator.resource())?, }; // Retrieve the finalize inputs. let inputs = match function.finalize_logic() { diff --git a/synthesizer/process/src/stack/helpers/sample.rs b/synthesizer/process/src/stack/helpers/sample.rs index b5c9ff2994..1cff323de9 100644 --- a/synthesizer/process/src/stack/helpers/sample.rs +++ b/synthesizer/process/src/stack/helpers/sample.rs @@ -161,10 +161,15 @@ impl Stack { depth: usize, rng: &mut R, ) -> Result> { + // Retrieve the external stack, if needed. + let external_stack = match locator.program_id() == self.program_id() { + true => None, + false => Some(self.get_external_stack(locator.program_id())?), + }; // Retrieve the associated function. - let function = match locator.program_id() == self.program_id() { - true => self.get_function_ref(locator.resource())?, - false => self.get_external_program(locator.program_id())?.get_function_ref(locator.resource())?, + let function = match &external_stack { + Some(external_stack) => external_stack.get_function_ref(locator.resource())?, + None => self.get_function_ref(locator.resource())?, }; // Retrieve the finalize inputs. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 283c15b8a3..a20e6d0628 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -71,7 +71,7 @@ use synthesizer_snark::{Certificate, ProvingKey, UniversalSRS, VerifyingKey}; use aleo_std::prelude::{finish, lap, timer}; use indexmap::IndexMap; use parking_lot::RwLock; -use std::sync::Arc; +use std::sync::{Arc, Weak}; #[cfg(not(feature = "serial"))] use rayon::prelude::*; @@ -175,8 +175,8 @@ impl CallStack { pub struct Stack { /// The program (record types, structs, functions). program: Program, - /// The mapping of external stacks as `(program ID, stack)`. - external_stacks: IndexMap, Arc>>, + /// A weak reference to the global stack map. + stacks: Weak, Arc>>>, /// The mapping of closure and function names to their register types. register_types: IndexMap, RegisterTypes>, /// The mapping of finalize names to their register types. @@ -256,42 +256,14 @@ impl StackProgram for Stack { self.edition } - /// Returns `true` if the stack contains the external record. - #[inline] - fn contains_external_record(&self, locator: &Locator) -> bool { - // Retrieve the external program. - match self.get_external_program(locator.program_id()) { - // Return `true` if the external record exists. - Ok(external_program) => external_program.contains_record(locator.resource()), - // Return `false` otherwise. - Err(_) => false, - } - } - /// Returns the external stack for the given program ID. #[inline] - fn get_external_stack(&self, program_id: &ProgramID) -> Result<&Arc>> { - // Retrieve the external stack. - self.external_stacks.get(program_id).ok_or_else(|| anyhow!("External program '{program_id}' does not exist.")) - } - - /// Returns the external program for the given program ID. - #[inline] - fn get_external_program(&self, program_id: &ProgramID) -> Result<&Program> { - match self.program.id() == program_id { - true => bail!("Attempted to get the main program '{}' as an external program", self.program.id()), - // Retrieve the external stack, and return the external program. - false => Ok(self.get_external_stack(program_id)?.program()), - } - } - - /// Returns the external record if the stack contains the external record. - #[inline] - fn get_external_record(&self, locator: &Locator) -> Result<&RecordType> { - // Retrieve the external program. - let external_program = self.get_external_program(locator.program_id())?; - // Return the external record, if it exists. - external_program.get_record(locator.resource()) + fn get_external_stack(&self, program_id: &ProgramID) -> Result>> { + // TODO (@d0cd), Check that stack is in imports + // Upgrade the reference to the global stack map. + let stacks = self.stacks.upgrade().ok_or_else(|| anyhow!("Global stack map does not exist."))?; + // Retrieve the stack. + stacks.get(program_id).cloned().ok_or_else(|| anyhow!("External program '{program_id}' does not exist.")) } /// Returns the expected finalize cost for the given function name. @@ -476,7 +448,6 @@ impl Stack { impl PartialEq for Stack { fn eq(&self, other: &Self) -> bool { self.program == other.program - && self.external_stacks == other.external_stacks && self.register_types == other.register_types && self.finalize_types == other.finalize_types } diff --git a/synthesizer/process/src/stack/register_types/initialize.rs b/synthesizer/process/src/stack/register_types/initialize.rs index 82bb47422e..d2ca6954ae 100644 --- a/synthesizer/process/src/stack/register_types/initialize.rs +++ b/synthesizer/process/src/stack/register_types/initialize.rs @@ -275,8 +275,10 @@ impl RegisterTypes { } } RegisterType::ExternalRecord(locator) => { + // Get the external stack. + let external_stack = stack.get_external_stack(locator.program_id())?; // Ensure the external record type is defined in the program. - if !stack.contains_external_record(locator) { + if !external_stack.program().contains_record(locator.resource()) { bail!("External record '{locator}' in '{}' is not defined.", stack.program_id()) } } @@ -327,8 +329,10 @@ impl RegisterTypes { } } RegisterType::ExternalRecord(locator) => { + // Get the external stack. + let external_stack = stack.get_external_stack(locator.program_id())?; // Ensure the external record type is defined in the program. - if !stack.contains_external_record(locator) { + if !external_stack.program().contains_record(locator.resource()) { bail!("External record '{locator}' in '{}' is not defined.", stack.program_id()) } } @@ -336,7 +340,9 @@ impl RegisterTypes { // Ensure that the locator is defined. match locator.program_id() == stack.program_id() { true => stack.get_function(locator.resource())?, - false => stack.get_external_program(locator.program_id())?.get_function(locator.resource())?, + false => { + stack.get_external_stack(locator.program_id())?.program().get_function(locator.resource())? + } }; } }; @@ -456,7 +462,8 @@ impl RegisterTypes { } // Retrieve the program. - let external = stack.get_external_program(program_id)?; + let external_stack = stack.get_external_stack(program_id)?; + let external = external_stack.program(); // Check that function exists in the program. if let Ok(child_function) = external.get_function_ref(resource) { // If the child function contains a finalize block, then the parent function must also contain a finalize block. diff --git a/synthesizer/process/src/stack/register_types/mod.rs b/synthesizer/process/src/stack/register_types/mod.rs index 16d7e5e59d..05515d6218 100644 --- a/synthesizer/process/src/stack/register_types/mod.rs +++ b/synthesizer/process/src/stack/register_types/mod.rs @@ -133,11 +133,11 @@ impl RegisterTypes { .iter(); // A helper enum to track the type of the register. - enum RegisterRefType<'a, N: Network> { + enum RegisterAccessType { /// A plaintext type. - Plaintext(&'a PlaintextType), + Plaintext(PlaintextType), /// A future. - Future(&'a Locator), + Future(Locator), } // A literal address type. @@ -147,7 +147,7 @@ impl RegisterTypes { // We perform a single access, if the register type is a record. // This is done to minimize the number of `clone` operations and simplify the code. let mut register_type = match register_type { - RegisterType::Plaintext(plaintext_type) => RegisterRefType::Plaintext(plaintext_type), + RegisterType::Plaintext(plaintext_type) => RegisterAccessType::Plaintext(plaintext_type.clone()), RegisterType::Record(record_name) => { // Ensure the record type exists. ensure!(stack.program().contains_record(record_name), "Record '{record_name}' does not exist"); @@ -157,7 +157,7 @@ impl RegisterTypes { // Retrieve the member type from the record. if access == &Access::Member(Identifier::from_str("owner")?) { // If the member is the owner, then output the address type. - RegisterRefType::Plaintext(&literal_address_type) + RegisterAccessType::Plaintext(literal_address_type) } else { // Retrieve the path name. let path_name = match access { @@ -167,21 +167,23 @@ impl RegisterTypes { // Retrieve the entry type from the record. match stack.program().get_record(record_name)?.entries().get(path_name) { // Retrieve the plaintext type. - Some(entry_type) => RegisterRefType::Plaintext(entry_type.plaintext_type()), + Some(entry_type) => RegisterAccessType::Plaintext(entry_type.plaintext_type().clone()), None => bail!("'{path_name}' does not exist in record '{record_name}'"), } } } RegisterType::ExternalRecord(locator) => { - // Ensure the external record type exists. - ensure!(stack.contains_external_record(locator), "External record '{locator}' does not exist"); + // Get the external stack. + let external_stack = stack.get_external_stack(locator.program_id())?; + // Get the external record. + let external_record = external_stack.program().get_record(locator.resource())?; // Retrieve the first access. // Note: this unwrap is safe since the path is checked to be non-empty above. let access = path_iter.next().unwrap(); // Retrieve the member type from the external record. if access == &Access::Member(Identifier::from_str("owner")?) { // If the member is the owner, then output the address type. - RegisterRefType::Plaintext(&literal_address_type) + RegisterAccessType::Plaintext(literal_address_type) } else { // Retrieve the path name. let path_name = match access { @@ -189,48 +191,51 @@ impl RegisterTypes { Access::Index(_) => bail!("Attempted to index into an external record"), }; // Retrieve the entry type from the external record. - match stack.get_external_record(locator)?.entries().get(path_name) { + match external_record.entries().get(path_name) { // Retrieve the plaintext type. - Some(entry_type) => RegisterRefType::Plaintext(entry_type.plaintext_type()), + Some(entry_type) => RegisterAccessType::Plaintext(entry_type.plaintext_type().clone()), None => bail!("'{path_name}' does not exist in external record '{locator}'"), } } } - RegisterType::Future(locator) => RegisterRefType::Future(locator), + RegisterType::Future(locator) => RegisterAccessType::Future(*locator), }; // Traverse the path to find the register type. for access in path_iter { // Update the plaintext type at each step. - match (register_type, access) { + match (®ister_type, access) { // Ensure the plaintext type is not a literal, as the register references an access. - (RegisterRefType::Plaintext(PlaintextType::Literal(..)), _) => { + (RegisterAccessType::Plaintext(PlaintextType::Literal(..)), _) => { bail!("'{register}' references a literal.") } // Traverse the path to output the register type. - (RegisterRefType::Plaintext(PlaintextType::Struct(struct_name)), Access::Member(identifier)) => { + (RegisterAccessType::Plaintext(PlaintextType::Struct(struct_name)), Access::Member(identifier)) => { // Retrieve the member type from the struct. match stack.program().get_struct(struct_name)?.members().get(identifier) { // Update the member type. - Some(member_type) => register_type = RegisterRefType::Plaintext(member_type), + Some(member_type) => register_type = RegisterAccessType::Plaintext(member_type.clone()), None => bail!("'{identifier}' does not exist in struct '{struct_name}'"), } } // Traverse the path to output the register type. - (RegisterRefType::Plaintext(PlaintextType::Array(array_type)), Access::Index(index)) => { + (RegisterAccessType::Plaintext(PlaintextType::Array(array_type)), Access::Index(index)) => { match index < array_type.length() { - true => register_type = RegisterRefType::Plaintext(array_type.next_element_type()), + true => register_type = RegisterAccessType::Plaintext(array_type.next_element_type().clone()), false => bail!("'{index}' is out of bounds for '{register}'"), } } // Access the input to the future to output the register type and check that it is in bounds. - (RegisterRefType::Future(locator), Access::Index(index)) => { + (RegisterAccessType::Future(locator), Access::Index(index)) => { + // Retrieve the external stack, if needed. + let external_stack = match locator.program_id() == stack.program_id() { + true => None, + false => Some(stack.get_external_stack(locator.program_id())?), + }; // Retrieve the associated function. - let function = match locator.program_id() == stack.program_id() { - true => stack.get_function_ref(locator.resource())?, - false => { - stack.get_external_program(locator.program_id())?.get_function_ref(locator.resource())? - } + let function = match &external_stack { + Some(external_stack) => external_stack.get_function_ref(locator.resource())?, + None => stack.get_function_ref(locator.resource())?, }; // Retrieve the finalize inputs. let finalize_inputs = match function.finalize_logic() { @@ -242,17 +247,19 @@ impl RegisterTypes { // Retrieve the input type and update `finalize_type` for the next iteration. Some(input) => { register_type = match input.finalize_type() { - FinalizeType::Plaintext(plaintext_type) => RegisterRefType::Plaintext(plaintext_type), - FinalizeType::Future(locator) => RegisterRefType::Future(locator), + FinalizeType::Plaintext(plaintext_type) => { + RegisterAccessType::Plaintext(plaintext_type.clone()) + } + FinalizeType::Future(locator) => RegisterAccessType::Future(*locator), } } // Halts if the index is out of bounds. None => bail!("Index out of bounds"), } } - (RegisterRefType::Plaintext(PlaintextType::Struct(..)), Access::Index(..)) - | (RegisterRefType::Plaintext(PlaintextType::Array(..)), Access::Member(..)) - | (RegisterRefType::Future(..), Access::Member(..)) => { + (RegisterAccessType::Plaintext(PlaintextType::Struct(..)), Access::Index(..)) + | (RegisterAccessType::Plaintext(PlaintextType::Array(..)), Access::Member(..)) + | (RegisterAccessType::Future(..), Access::Member(..)) => { bail!("Invalid access `{access}`") } } @@ -260,8 +267,8 @@ impl RegisterTypes { // Output the register type. Ok(match register_type { - RegisterRefType::Plaintext(plaintext_type) => RegisterType::Plaintext(plaintext_type.clone()), - RegisterRefType::Future(locator) => RegisterType::Future(*locator), + RegisterAccessType::Plaintext(plaintext_type) => RegisterType::Plaintext(plaintext_type.clone()), + RegisterAccessType::Future(locator) => RegisterType::Future(locator), }) } } diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index ad9daafa73..fd346f3a60 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -1255,7 +1255,7 @@ finalize compute: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // Initialize a new caller account. let caller_private_key = PrivateKey::::new(rng).unwrap(); @@ -1368,7 +1368,7 @@ finalize compute: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // Initialize a new caller account. let caller_private_key = PrivateKey::::new(rng).unwrap(); @@ -1495,7 +1495,7 @@ finalize mint_public: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // TODO (howardwu): Remove this. I call this to synthesize the proving key independent of the assignment from 'execute'. // In general, we should update all tests to utilize a presynthesized proving key, before execution, to test @@ -1624,7 +1624,7 @@ finalize mint_public: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // TODO (howardwu): Remove this. I call this to synthesize the proving key independent of the assignment from 'execute'. // In general, we should update all tests to utilize a presynthesized proving key, before execution, to test @@ -1664,7 +1664,7 @@ finalize init: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(2), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // TODO (howardwu): Remove this. I call this to synthesize the proving key independent of the assignment from 'execute'. // In general, we should update all tests to utilize a presynthesized proving key, before execution, to test @@ -1782,7 +1782,7 @@ finalize compute: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // Initialize a new caller account. let caller_private_key = PrivateKey::::new(rng).unwrap(); @@ -2211,7 +2211,7 @@ finalize compute: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // Initialize a new caller account. let caller_private_key = PrivateKey::::new(rng).unwrap(); @@ -2366,8 +2366,7 @@ fn test_process_deploy_credits_program() { // Initialize an empty process without the `credits` program. let empty_process = Process { universal_srs: Arc::new(UniversalSRS::::load().unwrap()), - stacks: IndexMap::new(), - editions: IndexMap::new(), + stacks: Arc::new(IndexMap::new()), }; // Construct the process. @@ -2441,7 +2440,7 @@ function {function_name}: // Finalize the deployment. let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // Initialize a new caller account. let caller_private_key = PrivateKey::::new(rng).unwrap(); diff --git a/synthesizer/program/src/logic/instruction/operation/call.rs b/synthesizer/program/src/logic/instruction/operation/call.rs index b5cbaf8d51..f2b2c49986 100644 --- a/synthesizer/program/src/logic/instruction/operation/call.rs +++ b/synthesizer/program/src/logic/instruction/operation/call.rs @@ -154,8 +154,10 @@ impl Call { match self.operator() { // Check if the locator is for a function. CallOperator::Locator(locator) => { + // Get the external stack. + let external_stack = stack.get_external_stack(locator.program_id())?; // Retrieve the program. - let program = stack.get_external_program(locator.program_id())?; + let program = external_stack.program(); // Check if the resource is a function. Ok(program.contains_function(locator.resource())) } @@ -195,11 +197,10 @@ impl Call { stack: &impl StackProgram, input_types: &[RegisterType], ) -> Result>> { - // Retrieve the program and resource. - let (is_external, program, resource) = match &self.operator { - // Retrieve the program and resource from the locator. + // Retrieve the external stack, if needed, and the resource. + let (external_stack, resource) = match &self.operator { CallOperator::Locator(locator) => { - (true, stack.get_external_program(locator.program_id())?, locator.resource()) + (Some(stack.get_external_stack(locator.program_id())?), locator.resource()) } CallOperator::Resource(resource) => { // TODO (howardwu): Revisit this decision to forbid calling internal functions. A record cannot be spent again. @@ -208,11 +209,14 @@ impl Call { if stack.program().contains_function(resource) { bail!("Cannot call '{resource}'. Use a closure ('closure {resource}:') instead.") } - - (false, stack.program(), resource) + (None, resource) } }; - + // Retrieve the program. + let (is_external, program) = match &external_stack { + Some(external_stack) => (true, external_stack.program()), + None => (false, stack.program()), + }; // If the operator is a closure, retrieve the closure and compute the output types. if let Ok(closure) = program.get_closure(resource) { // Ensure the number of operands matches the number of input statements. diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index 07c7bfe08d..4db069cb79 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -29,7 +29,6 @@ use console::{ PlaintextType, ProgramID, Record, - RecordType, Register, RegisterType, Value, @@ -75,17 +74,8 @@ pub trait StackProgram { /// Returns the program edition. fn edition(&self) -> u16; - /// Returns `true` if the stack contains the external record. - fn contains_external_record(&self, locator: &Locator) -> bool; - /// Returns the external stack for the given program ID. - fn get_external_stack(&self, program_id: &ProgramID) -> Result<&Arc>; - - /// Returns the external program for the given program ID. - fn get_external_program(&self, program_id: &ProgramID) -> Result<&Program>; - - /// Returns `true` if the stack contains the external record. - fn get_external_record(&self, locator: &Locator) -> Result<&RecordType>; + fn get_external_stack(&self, program_id: &ProgramID) -> Result>; /// Returns the expected finalize cost for the given function name. fn get_finalize_cost(&self, function_name: &Identifier) -> Result; diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index f9a9e2f87f..ee6a3c8539 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -831,7 +831,13 @@ impl> VM { // Commit all of the stacks to the process. if !stacks.is_empty() { - stacks.into_iter().for_each(|stack| process.add_stack(stack)) + for stack in stacks { + if let Err(e) = process.add_stack(stack) { + eprintln!("Critical bug in finalize: {e}"); + // Note: This will abort the entire atomic batch. + return Err(format!("Failed to commit the stack - {e}")); + } + } } finish!(timer); // <- Note: This timer does **not** include the time to write batch to DB. From bc7855c72f7ec7c922801ec8fb8da6317671fada Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 30 Jan 2025 17:26:42 -0800 Subject: [PATCH 11/46] Clippy --- synthesizer/process/src/finalize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 4d977db6d1..3901d01b8a 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -605,7 +605,7 @@ function compute: let (stack, _) = process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap(); // Add the stack *manually* to the process. - process.add_stack(stack); + process.add_stack(stack).unwrap(); // Ensure the program exists. assert!(process.contains_program(program.id())); From 3825922ad55ea04fe2609cf4afd08d1276103b31 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:16:01 -0800 Subject: [PATCH 12/46] Add to deployment DB --- .../store/src/helpers/memory/transaction.rs | 23 +- .../store/src/helpers/rocksdb/internal/id.rs | 4 + .../store/src/helpers/rocksdb/transaction.rs | 23 +- ledger/store/src/transaction/deployment.rs | 311 ++++++++++++++---- 4 files changed, 292 insertions(+), 69 deletions(-) diff --git a/ledger/store/src/helpers/memory/transaction.rs b/ledger/store/src/helpers/memory/transaction.rs index 31dc8ac3d3..e0eaa967d0 100644 --- a/ledger/store/src/helpers/memory/transaction.rs +++ b/ledger/store/src/helpers/memory/transaction.rs @@ -90,8 +90,10 @@ impl TransactionStorage for TransactionMemory { #[derive(Clone)] #[allow(clippy::type_complexity)] pub struct DeploymentMemory { - /// The ID map. - id_map: MemoryMap>, + /// The V1 ID map. + id_map_v1: MemoryMap>, + /// The V2 ID map. + id_map_v2: MemoryMap, u16)>, /// The edition map. edition_map: MemoryMap, u16>, /// The reverse ID map. @@ -110,7 +112,8 @@ pub struct DeploymentMemory { #[rustfmt::skip] impl DeploymentStorage for DeploymentMemory { - type IDMap = MemoryMap>; + type IDMapV1 = MemoryMap>; + type IDMapV2 = MemoryMap, u16)>; type EditionMap = MemoryMap, u16>; type ReverseIDMap = MemoryMap<(ProgramID, u16), N::TransactionID>; type OwnerMap = MemoryMap<(ProgramID, u16), ProgramOwner>; @@ -122,7 +125,8 @@ impl DeploymentStorage for DeploymentMemory { /// Initializes the deployment storage. fn open(fee_store: FeeStore) -> Result { Ok(Self { - id_map: MemoryMap::default(), + id_map_v1: MemoryMap::default(), + id_map_v2: MemoryMap::default(), edition_map: MemoryMap::default(), reverse_id_map: MemoryMap::default(), owner_map: MemoryMap::default(), @@ -133,9 +137,14 @@ impl DeploymentStorage for DeploymentMemory { }) } - /// Returns the ID map. - fn id_map(&self) -> &Self::IDMap { - &self.id_map + /// Returns the V1 ID map. + fn id_map_v1(&self) -> &Self::IDMapV1 { + &self.id_map_v1 + } + + /// Returns the V2 ID map. + fn id_map_v2(&self) -> &Self::IDMapV2 { + &self.id_map_v2 } /// Returns the edition map. diff --git a/ledger/store/src/helpers/rocksdb/internal/id.rs b/ledger/store/src/helpers/rocksdb/internal/id.rs index b01dc7b99c..a2d4c2cbdf 100644 --- a/ledger/store/src/helpers/rocksdb/internal/id.rs +++ b/ledger/store/src/helpers/rocksdb/internal/id.rs @@ -106,6 +106,7 @@ pub enum CommitteeMap { #[repr(u16)] pub enum DeploymentMap { ID = DataID::DeploymentIDMap as u16, + IDV2 = DataID::DeploymentIDV2Map as u16, Edition = DataID::DeploymentEditionMap as u16, ReverseID = DataID::DeploymentReverseIDMap as u16, Owner = DataID::DeploymentOwnerMap as u16, @@ -305,4 +306,7 @@ enum DataID { Test4, #[cfg(test)] Test5, + + // Additional + DeploymentIDV2Map, } diff --git a/ledger/store/src/helpers/rocksdb/transaction.rs b/ledger/store/src/helpers/rocksdb/transaction.rs index fbb026a5c9..9a9320d9f3 100644 --- a/ledger/store/src/helpers/rocksdb/transaction.rs +++ b/ledger/store/src/helpers/rocksdb/transaction.rs @@ -100,8 +100,10 @@ impl TransactionStorage for TransactionDB { #[derive(Clone)] #[allow(clippy::type_complexity)] pub struct DeploymentDB { - /// The ID map. - id_map: DataMap>, + /// The V1 ID map. + id_map_v1: DataMap>, + /// The V2 ID map. + id_map_v2: DataMap, u16)>, /// The edition map. edition_map: DataMap, u16>, /// The reverse ID map. @@ -120,7 +122,8 @@ pub struct DeploymentDB { #[rustfmt::skip] impl DeploymentStorage for DeploymentDB { - type IDMap = DataMap>; + type IDMapV1 = DataMap>; + type IDMapV2 = DataMap, u16)>; type EditionMap = DataMap, u16>; type ReverseIDMap = DataMap<(ProgramID, u16), N::TransactionID>; type OwnerMap = DataMap<(ProgramID, u16), ProgramOwner>; @@ -134,7 +137,8 @@ impl DeploymentStorage for DeploymentDB { // Retrieve the storage mode. let storage_mode = fee_store.storage_mode(); Ok(Self { - id_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::ID))?, + id_map_v1: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::ID))?, + id_map_v2: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::IDV2))?, edition_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Edition))?, reverse_id_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::ReverseID))?, owner_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Owner))?, @@ -145,11 +149,16 @@ impl DeploymentStorage for DeploymentDB { }) } - /// Returns the ID map. - fn id_map(&self) -> &Self::IDMap { - &self.id_map + /// Returns the V1 ID map. + fn id_map_v1(&self) -> &Self::IDMapV1 { + &self.id_map_v1 } + /// Returns the V2 ID map. + fn id_map_v2(&self) -> &Self::IDMapV2 { + &self.id_map_v2 + } + /// Returns the edition map. fn edition_map(&self) -> &Self::EditionMap { &self.edition_map diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index 242bc20cbb..0a11096985 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -34,10 +34,14 @@ use anyhow::Result; use core::marker::PhantomData; use std::borrow::Cow; +// TODO (@d0cd): Review migration. + /// A trait for deployment storage. pub trait DeploymentStorage: Clone + Send + Sync { /// The mapping of `transaction ID` to `program ID`. - type IDMap: for<'a> Map<'a, N::TransactionID, ProgramID>; + type IDMapV1: for<'a> Map<'a, N::TransactionID, ProgramID>; + /// The mapping of `transaction ID` to `(program ID, edition)`. + type IDMapV2: for<'a> Map<'a, N::TransactionID, (ProgramID, u16)>; /// The mapping of `program ID` to `edition`. type EditionMap: for<'a> Map<'a, ProgramID, u16>; /// The mapping of `(program ID, edition)` to `transaction ID`. @@ -57,7 +61,9 @@ pub trait DeploymentStorage: Clone + Send + Sync { fn open(fee_store: FeeStore) -> Result; /// Returns the ID map. - fn id_map(&self) -> &Self::IDMap; + fn id_map_v1(&self) -> &Self::IDMapV1; + /// Returns the V2 ID map. + fn id_map_v2(&self) -> &Self::IDMapV2; /// Returns the edition map. fn edition_map(&self) -> &Self::EditionMap; /// Returns the reverse ID map. @@ -80,7 +86,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { /// Starts an atomic batch write operation. fn start_atomic(&self) { - self.id_map().start_atomic(); + self.id_map_v1().start_atomic(); + self.id_map_v2().start_atomic(); self.edition_map().start_atomic(); self.reverse_id_map().start_atomic(); self.owner_map().start_atomic(); @@ -92,7 +99,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { /// Checks if an atomic batch is in progress. fn is_atomic_in_progress(&self) -> bool { - self.id_map().is_atomic_in_progress() + self.id_map_v1().is_atomic_in_progress() + || self.id_map_v2().is_atomic_in_progress() || self.edition_map().is_atomic_in_progress() || self.reverse_id_map().is_atomic_in_progress() || self.owner_map().is_atomic_in_progress() @@ -104,7 +112,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { /// Checkpoints the atomic batch. fn atomic_checkpoint(&self) { - self.id_map().atomic_checkpoint(); + self.id_map_v1().atomic_checkpoint(); + self.id_map_v2().atomic_checkpoint(); self.edition_map().atomic_checkpoint(); self.reverse_id_map().atomic_checkpoint(); self.owner_map().atomic_checkpoint(); @@ -116,7 +125,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { /// Clears the latest atomic batch checkpoint. fn clear_latest_checkpoint(&self) { - self.id_map().clear_latest_checkpoint(); + self.id_map_v1().clear_latest_checkpoint(); + self.id_map_v2().clear_latest_checkpoint(); self.edition_map().clear_latest_checkpoint(); self.reverse_id_map().clear_latest_checkpoint(); self.owner_map().clear_latest_checkpoint(); @@ -128,7 +138,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { /// Rewinds the atomic batch to the previous checkpoint. fn atomic_rewind(&self) { - self.id_map().atomic_rewind(); + self.id_map_v1().atomic_rewind(); + self.id_map_v2().atomic_rewind(); self.edition_map().atomic_rewind(); self.reverse_id_map().atomic_rewind(); self.owner_map().atomic_rewind(); @@ -140,7 +151,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { /// Aborts an atomic batch write operation. fn abort_atomic(&self) { - self.id_map().abort_atomic(); + self.id_map_v1().abort_atomic(); + self.id_map_v2().abort_atomic(); self.edition_map().abort_atomic(); self.reverse_id_map().abort_atomic(); self.owner_map().abort_atomic(); @@ -152,7 +164,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { /// Finishes an atomic batch write operation. fn finish_atomic(&self) -> Result<()> { - self.id_map().finish_atomic()?; + self.id_map_v1().finish_atomic()?; + self.id_map_v2().finish_atomic()?; self.edition_map().finish_atomic()?; self.reverse_id_map().finish_atomic()?; self.owner_map().finish_atomic()?; @@ -183,12 +196,11 @@ pub trait DeploymentStorage: Clone + Send + Sync { // Retrieve the program ID. let program_id = *program.id(); - // TODO (@d0cd) Should we add a safety check for incremental update on the edition map? - atomic_batch_scope!(self, { - // Store the program ID. - self.id_map().insert(*transaction_id, program_id)?; - // Store the edition. + // Store the program ID and edition. + // Note that the `id_map_v1` is intentionally excluded updated. + self.id_map_v2().insert(*transaction_id, (program_id, edition))?; + // Store the latest edition. self.edition_map().insert(program_id, edition)?; // Store the reverse program ID. @@ -213,16 +225,16 @@ pub trait DeploymentStorage: Clone + Send + Sync { }) } + /// TODO (@d0cd) After the migration only the V2 map is used, so do we need to handle the old map? /// Removes the deployment transaction for the given `transaction ID`. - // TODO (@d0cd) This removal needs to be done with the reverse map. fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> { - // Retrieve the program ID. - let program_id = match self.get_program_id(transaction_id)? { - Some(edition) => edition, + // Retrieve the program ID and edition from the ID map. + let (program_id, edition) = match self.get_program_id_and_edition(transaction_id)? { + Some((program_id, edition)) => (program_id, edition), None => bail!("Failed to get the program ID for transaction '{transaction_id}'"), }; - // Retrieve the edition. - let edition = match self.get_edition(&program_id)? { + // Get the latest edition of the program. + let latest_edition = match self.get_edition(&program_id)? { Some(edition) => edition, None => bail!("Failed to locate the edition for program '{program_id}'"), }; @@ -231,12 +243,27 @@ pub trait DeploymentStorage: Clone + Send + Sync { Some(program) => cow_to_cloned!(program), None => bail!("Failed to locate program '{program_id}' for transaction '{transaction_id}'"), }; + // Check if the program and edition are in the old ID map. + let in_v1_id_map = self.id_map_v1().contains_key_confirmed(transaction_id)?; atomic_batch_scope!(self, { // Remove the program ID. - self.id_map().remove(transaction_id)?; - // Remove the edition. - self.edition_map().remove(&program_id)?; + if in_v1_id_map { + self.id_map_v1().remove(transaction_id)?; + } else { + self.id_map_v2().remove(transaction_id)?; + } + // Update the latest edition. + match (edition, latest_edition) { + // If the removed edition is 0, remove the program ID from the edition map. + (0, _) => self.edition_map().remove(&program_id)?, + // If the removed edition is the latest one, update the edition map. + (edition, latest_edition) if edition == latest_edition => { + self.edition_map().insert(program_id, edition.saturating_sub(1))? + } + // Otherwise, do nothing. + _ => {} + } // Remove the reverse program ID. self.reverse_id_map().remove(&(program_id, edition))?; @@ -260,8 +287,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { }) } - /// Returns the transaction ID that contains the given `program ID`. - // TODO (@d0cd) Define semantics, OG deployment or latest? + /// Returns the latest transaction ID that contains the given `program ID`. fn find_transaction_id_from_program_id(&self, program_id: &ProgramID) -> Result> { // Check if the program ID is for 'credits.aleo'. // This case is handled separately, as it is a default program of the VM. @@ -269,7 +295,6 @@ pub trait DeploymentStorage: Clone + Send + Sync { if program_id == &ProgramID::from_str("credits.aleo")? { return Ok(None); } - // Retrieve the edition. let edition = match self.get_edition(program_id)? { Some(edition) => edition, @@ -282,6 +307,25 @@ pub trait DeploymentStorage: Clone + Send + Sync { } } + /// Returns the transaction ID that contains the given `program ID` and `edition`. + fn find_transaction_id_from_program_id_and_edition( + &self, + program_id: &ProgramID, + edition: u16, + ) -> Result> { + // Check if the pogram ID is for `credits.aleo`. + // This case is handled separately, as it is a default program of the VM. + // TODO: Note on removal in `find_transaction_id_from_program_id`. + if program_id == &ProgramID::from_str("credits.aleo")? { + return Ok(None); + } + // Retrieve the transaction ID. + match self.reverse_id_map().get_confirmed(&(*program_id, edition))? { + Some(transaction_id) => Ok(Some(cow_to_copied!(transaction_id))), + None => Ok(None), + } + } + /// Returns the transaction ID that contains the given `transition ID`. fn find_transaction_id_from_transition_id( &self, @@ -290,16 +334,28 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.fee_store().find_transaction_id_from_transition_id(transition_id) } + /// Returns the program ID and edition for the given `transaction ID`. + /// Default to the V1 ID map if it does not exist in the V2 ID map. + fn get_program_id_and_edition(&self, transaction_id: &N::TransactionID) -> Result, u16)>> { + match self.id_map_v2().get_confirmed(transaction_id)? { + Some(result) => Ok(Some(cow_to_copied!(result))), + None => { + let Some(program_id) = self.id_map_v1().get_confirmed(transaction_id)? else { + return Ok(None); + }; + let Some(edition) = self.get_edition(&program_id)? else { return Ok(None) }; + Ok(Some((cow_to_copied!(program_id), edition))) + } + } + } + /// Returns the program ID for the given `transaction ID`. fn get_program_id(&self, transaction_id: &N::TransactionID) -> Result>> { // Retrieve the program ID. - match self.id_map().get_confirmed(transaction_id)? { - Some(program_id) => Ok(Some(cow_to_copied!(program_id))), - None => Ok(None), - } + Ok(self.get_program_id_and_edition(transaction_id)?.map(|(program_id, _)| program_id)) } - /// Returns the edition for the given `program ID`. + /// Returns the latest edition for the given `program ID`. fn get_edition(&self, program_id: &ProgramID) -> Result> { // Check if the program ID is for 'credits.aleo'. // This case is handled separately, as it is a default program of the VM. @@ -314,7 +370,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { } } - /// Returns the program for the given `program ID`. + /// Returns the latest program for the given `program ID`. fn get_program(&self, program_id: &ProgramID) -> Result>> { // Check if the program ID is for 'credits.aleo'. // This case is handled separately, as it is a default program of the VM. @@ -322,7 +378,6 @@ pub trait DeploymentStorage: Clone + Send + Sync { if program_id == &ProgramID::from_str("credits.aleo")? { return Ok(Some(Program::credits()?)); } - // Retrieve the edition. let edition = match self.get_edition(program_id)? { Some(edition) => edition, @@ -335,7 +390,22 @@ pub trait DeploymentStorage: Clone + Send + Sync { } } - /// Returns the verifying key for the given `program ID` and `function name`. + /// Returns the latest program for the given `program ID` and `edition`. + fn get_program_with_edition(&self, program_id: &ProgramID, edition: u16) -> Result>> { + // Check if the program ID is for 'credits.aleo'. + // This case is handled separately, as it is a default program of the VM. + // TODO (howardwu): After we update 'fee' rules and 'Ratify' in genesis, we can remove this. + if program_id == &ProgramID::from_str("credits.aleo")? { + return Ok(Some(Program::credits()?)); + } + // Retrieve the program. + match self.program_map().get_confirmed(&(*program_id, edition))? { + Some(program) => Ok(Some(cow_to_cloned!(program))), + None => bail!("Failed to get program '{program_id}' (edition {edition})"), + } + } + + /// Returns the latest verifying key for the given `program ID` and `function name`. fn get_verifying_key( &self, program_id: &ProgramID, @@ -367,6 +437,33 @@ pub trait DeploymentStorage: Clone + Send + Sync { } } + /// Returns the verifying key for the given `program ID`, `function name` and `edition`. + fn get_verifying_key_with_edition( + &self, + program_id: &ProgramID, + function_name: &Identifier, + edition: u16, + ) -> Result>> { + // Check if the program ID is for 'credits.aleo'. + // This case is handled separately, as it is a default program of the VM. + // TODO (howardwu): After we update 'fee' rules and 'Ratify' in genesis, we can remove this. + if program_id == &ProgramID::from_str("credits.aleo")? { + // Load the verifying key. + let verifying_key = N::get_credits_verifying_key(function_name.to_string())?; + // Retrieve the number of public and private variables. + // Note: This number does *NOT* include the number of constants. This is safe because + // this program is never deployed, as it is a first-class citizen of the protocol. + let num_variables = verifying_key.circuit_info.num_public_and_private_variables as u64; + // Return the verifying key. + return Ok(Some(VerifyingKey::new(verifying_key.clone(), num_variables))); + } + // Retrieve the verifying key. + match self.verifying_key_map().get_confirmed(&(*program_id, *function_name, edition))? { + Some(verifying_key) => Ok(Some(cow_to_cloned!(verifying_key))), + None => bail!("Failed to get the verifying key for '{program_id}/{function_name}' (edition {edition})"), + } + } + /// Returns the certificate for the given `program ID` and `function name`. fn get_certificate( &self, @@ -392,19 +489,34 @@ pub trait DeploymentStorage: Clone + Send + Sync { } } + /// Returns the certificate for the given `program ID`, `function name`, and `edition`. + fn get_certificate_with_edition( + &self, + program_id: &ProgramID, + function_name: &Identifier, + edition: u16, + ) -> Result>> { + // Check if the program ID is for 'credits.aleo'. + // This case is handled separately, as it is a default program of the VM. + // TODO (howardwu): After we update 'fee' rules and 'Ratify' in genesis, we can remove this. + if program_id == &ProgramID::from_str("credits.aleo")? { + return Ok(None); + } + // Retrieve the certificate. + match self.certificate_map().get_confirmed(&(*program_id, *function_name, edition))? { + Some(certificate) => Ok(Some(cow_to_cloned!(certificate))), + None => bail!("Failed to get the certificate for '{program_id}/{function_name}' (edition {edition})"), + } + } + /// Returns the deployment for the given `transaction ID`. // TODO (@d0cd) This should be done with IDMapV2. fn get_deployment(&self, transaction_id: &N::TransactionID) -> Result>> { - // Retrieve the program ID. - let program_id = match self.get_program_id(transaction_id)? { - Some(edition) => edition, + // Retrieve the program ID and edition. + let (program_id, edition) = match self.get_program_id_and_edition(transaction_id)? { + Some((program_id, edition)) => (program_id, edition), None => return Ok(None), }; - // Retrieve the edition. - let edition = match self.get_edition(&program_id)? { - Some(edition) => edition, - None => bail!("Failed to get the edition for program '{program_id}'"), - }; // Retrieve the program. let program = match self.program_map().get_confirmed(&(program_id, edition))? { Some(program) => cow_to_cloned!(program), @@ -439,7 +551,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.fee_store().get_fee(transaction_id) } - /// Returns the owner for the given `program ID`. + /// Returns the latest owner for the given `program ID`. fn get_owner(&self, program_id: &ProgramID) -> Result>> { // Check if the program ID is for 'credits.aleo'. // This case is handled separately, as it is a default program of the VM. @@ -462,6 +574,21 @@ pub trait DeploymentStorage: Clone + Send + Sync { } } + /// Returns the owner for the given `program ID` and `edition`. + fn get_owner_with_edition(&self, program_id: &ProgramID, edition: u16) -> Result>> { + // Check if the program ID is for 'credits.aleo'. + // This case is handled separately, as it is a default program of the VM. + // TODO (howardwu): After we update 'fee' rules and 'Ratify' in genesis, we can remove this. + if program_id == &ProgramID::from_str("credits.aleo")? { + return Ok(None); + } + // Retrieve the owner. + match self.owner_map().get_confirmed(&(*program_id, edition))? { + Some(owner) => Ok(Some(cow_to_copied!(owner))), + None => bail!("Failed to find the Owner for program '{program_id}' (edition {edition})"), + } + } + /// Returns the transaction for the given `transaction ID`. fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result>> { // Retrieve the deployment. @@ -476,7 +603,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { }; // Retrieve the owner. - let owner = match self.get_owner(deployment.program_id())? { + let owner = match self.get_owner_with_edition(deployment.program_id(), deployment.edition())? { Some(owner) => owner, None => bail!("Failed to get the owner for transaction '{transaction_id}'"), }; @@ -576,27 +703,37 @@ impl> DeploymentStore { self.storage.get_deployment(transaction_id) } - /// Returns the edition for the given `program ID`. + /// Returns the latest edition for the given `program ID`. pub fn get_edition(&self, program_id: &ProgramID) -> Result> { self.storage.get_edition(program_id) } - /// Returns the owner for the given `program ID`. + /// Returns the latest owner for the given `program ID`. pub fn get_owner(&self, program_id: &ProgramID) -> Result>> { self.storage.get_owner(program_id) } + /// Returns the owner for the given `program ID` and `edition`. + pub fn get_owner_with_edition(&self, program_id: &ProgramID, edition: u16) -> Result>> { + self.storage.get_owner_with_edition(program_id, edition) + } + /// Returns the program ID for the given `transaction ID`. pub fn get_program_id(&self, transaction_id: &N::TransactionID) -> Result>> { self.storage.get_program_id(transaction_id) } - /// Returns the program for the given `program ID`. + /// Returns the latest program for the given `program ID`. pub fn get_program(&self, program_id: &ProgramID) -> Result>> { self.storage.get_program(program_id) } - /// Returns the verifying key for the given `(program ID, function name)`. + /// Returns the program for the given `program ID` and `edition`. + pub fn get_program_with_edition(&self, program_id: &ProgramID, edition: u16) -> Result>> { + self.storage.get_program_with_edition(program_id, edition) + } + + /// Returns the latest verifying key for the given `(program ID, function name)`. pub fn get_verifying_key( &self, program_id: &ProgramID, @@ -605,6 +742,16 @@ impl> DeploymentStore { self.storage.get_verifying_key(program_id, function_name) } + /// Returns the verifying key for the given `program ID`, `function name`, and `edition`. + pub fn get_verifying_key_with_edition( + &self, + program_id: &ProgramID, + function_name: &Identifier, + edition: u16, + ) -> Result>> { + self.storage.get_verifying_key_with_edition(program_id, function_name, edition) + } + /// Returns the certificate for the given `(program ID, function name)`. pub fn get_certificate( &self, @@ -614,6 +761,16 @@ impl> DeploymentStore { self.storage.get_certificate(program_id, function_name) } + /// Returns the certificate for the given `program ID`, `function name`, and `edition`. + pub fn get_certificate_with_edition( + &self, + program_id: &ProgramID, + function_name: &Identifier, + edition: u16, + ) -> Result>> { + self.storage.get_certificate_with_edition(program_id, function_name, edition) + } + /// Returns the fee for the given `transaction ID`. pub fn get_fee(&self, transaction_id: &N::TransactionID) -> Result>> { self.storage.get_fee(transaction_id) @@ -626,6 +783,15 @@ impl> DeploymentStore { self.storage.find_transaction_id_from_program_id(program_id) } + /// Returns the transaction ID that deployed the given `program ID` and `edition`. + pub fn find_transaction_id_from_program_id_and_edition( + &self, + program_id: &ProgramID, + edition: u16, + ) -> Result> { + self.storage.find_transaction_id_from_program_id_and_edition(program_id, edition) + } + /// Returns the transaction ID that deployed the given `transition ID`. pub fn find_transaction_id_from_transition_id( &self, @@ -640,25 +806,52 @@ impl> DeploymentStore { pub fn contains_program_id(&self, program_id: &ProgramID) -> Result { self.storage.edition_map().contains_key_confirmed(program_id) } + + /// Returns `true` if the given program ID and edition exists. + pub fn contains_program_id_and_edition(&self, program_id: &ProgramID, edition: u16) -> Result { + self.storage.reverse_id_map().contains_key_confirmed(&(*program_id, edition)) + } } impl> DeploymentStore { /// Returns an iterator over the deployment transaction IDs, for all deployments. pub fn deployment_transaction_ids(&self) -> impl '_ + Iterator> { - self.storage.id_map().keys_confirmed() + self.storage.id_map_v1().keys_confirmed().chain(self.storage.id_map_v2().keys_confirmed()) } /// Returns an iterator over the program IDs, for all deployments. - // TODO (@d0cd) This can have duplicates. + /// Note that this can have duplicates. pub fn program_ids(&self) -> impl '_ + Iterator>> { - self.storage.id_map().values_confirmed().map(|id| match id { - Cow::Borrowed(id) => Cow::Borrowed(id), - Cow::Owned(id) => Cow::Owned(id), - }) + self.storage + .id_map_v1() + .values_confirmed() + .map(|id| match id { + Cow::Borrowed(id) => Cow::Borrowed(id), + Cow::Owned(id) => Cow::Owned(id), + }) + .chain(self.storage.id_map_v2().values_confirmed().map(|id| match id { + Cow::Borrowed(id) => Cow::Borrowed(&id.0), + Cow::Owned(id) => Cow::Owned(id.0), + })) + } + + /// Returns an iterators over the program IDs and editions, for all deployments. + pub fn program_ids_and_editions(&self) -> impl '_ + Iterator>, u16)> { + self.storage + .id_map_v1() + .values_confirmed() + .map(|id| match id { + Cow::Borrowed(id) => (Cow::Borrowed(id), N::EDITION), + Cow::Owned(id) => (Cow::Owned(id), N::EDITION), + }) + .chain(self.storage.id_map_v2().values_confirmed().map(|id| match id { + Cow::Borrowed(id) => (Cow::Borrowed(&id.0), id.1), + Cow::Owned(id) => (Cow::Owned(id.0), id.1), + })) } /// Returns an iterator over the programs, for all deployments. - // TODO (@d0cd) This would contain all versions of the program + /// Note that this may contain multiple versions of the same program. pub fn programs(&self) -> impl '_ + Iterator>> { self.storage.program_map().values_confirmed().map(|program| match program { Cow::Borrowed(program) => Cow::Borrowed(program), @@ -666,6 +859,14 @@ impl> DeploymentStore { }) } + /// Returns an iterator over the programs and their respective editions, for all deployments. + pub fn programs_and_editions(&self) -> impl '_ + Iterator>, u16)> { + self.storage.program_map().iter_confirmed().map(|(key, value)| match value { + Cow::Borrowed(program) => (Cow::Borrowed(program), key.1), + Cow::Owned(program) => (Cow::Owned(program), key.1), + }) + } + /// Returns an iterator over the `((program ID, function name, edition), verifying key)`, for all deployments. pub fn verifying_keys( &self, From 9b5c716aeb6d82bf809aa5407af2d375b569d848 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 4 Feb 2025 06:42:26 -0800 Subject: [PATCH 13/46] Rename DeploymentMap::ID to IDV1 --- ledger/store/src/helpers/rocksdb/internal/id.rs | 4 ++-- ledger/store/src/helpers/rocksdb/transaction.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ledger/store/src/helpers/rocksdb/internal/id.rs b/ledger/store/src/helpers/rocksdb/internal/id.rs index a2d4c2cbdf..b98e686abe 100644 --- a/ledger/store/src/helpers/rocksdb/internal/id.rs +++ b/ledger/store/src/helpers/rocksdb/internal/id.rs @@ -105,7 +105,7 @@ pub enum CommitteeMap { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(u16)] pub enum DeploymentMap { - ID = DataID::DeploymentIDMap as u16, + IDV1 = DataID::DeploymentIDV1Map as u16, IDV2 = DataID::DeploymentIDV2Map as u16, Edition = DataID::DeploymentEditionMap as u16, ReverseID = DataID::DeploymentReverseIDMap as u16, @@ -249,7 +249,7 @@ enum DataID { RoundToHeightMap, CommitteeMap, // Deployment - DeploymentIDMap, + DeploymentIDV1Map, DeploymentEditionMap, DeploymentReverseIDMap, DeploymentOwnerMap, diff --git a/ledger/store/src/helpers/rocksdb/transaction.rs b/ledger/store/src/helpers/rocksdb/transaction.rs index 9a9320d9f3..26a3e32f0a 100644 --- a/ledger/store/src/helpers/rocksdb/transaction.rs +++ b/ledger/store/src/helpers/rocksdb/transaction.rs @@ -137,7 +137,7 @@ impl DeploymentStorage for DeploymentDB { // Retrieve the storage mode. let storage_mode = fee_store.storage_mode(); Ok(Self { - id_map_v1: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::ID))?, + id_map_v1: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::IDV1))?, id_map_v2: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::IDV2))?, edition_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Edition))?, reverse_id_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::ReverseID))?, From 35cac79090efe33e1ed4839d1f8eb78269f72bbc Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:02:48 -0800 Subject: [PATCH 14/46] Resolve some TODOs, API cleanup --- ledger/benches/transaction.rs | 2 +- .../block/src/transaction/deployment/mod.rs | 6 - .../puzzle/epoch/src/synthesis/program/mod.rs | 2 +- ledger/src/tests.rs | 2 +- ledger/store/src/transaction/deployment.rs | 6 +- ledger/store/src/transaction/mod.rs | 6 +- ledger/test-helpers/src/lib.rs | 2 +- .../process/benches/stack_operations.rs | 6 +- synthesizer/process/src/deploy.rs | 11 +- synthesizer/process/src/finalize.rs | 88 ++++-------- synthesizer/process/src/lib.rs | 23 ++- synthesizer/process/src/stack/deploy.rs | 2 +- .../process/src/stack/helpers/check_update.rs | 6 +- .../process/src/stack/helpers/initialize.rs | 2 +- synthesizer/process/src/stack/mod.rs | 23 ++- synthesizer/process/src/tests/test_execute.rs | 28 ++-- synthesizer/process/src/verify_deployment.rs | 8 +- .../program/tests/instruction/assert.rs | 2 +- .../program/tests/instruction/commit.rs | 2 +- synthesizer/program/tests/instruction/hash.rs | 2 +- synthesizer/program/tests/instruction/is.rs | 2 +- synthesizer/src/vm/deploy.rs | 14 +- synthesizer/src/vm/finalize.rs | 136 +++++------------- synthesizer/src/vm/mod.rs | 3 - synthesizer/tests/test_process_execute.rs | 2 +- vm/file/prover.rs | 2 +- vm/file/verifier.rs | 2 +- vm/package/deploy.rs | 2 +- vm/package/mod.rs | 4 +- 29 files changed, 167 insertions(+), 229 deletions(-) diff --git a/ledger/benches/transaction.rs b/ledger/benches/transaction.rs index 194174544c..933b0918c0 100644 --- a/ledger/benches/transaction.rs +++ b/ledger/benches/transaction.rs @@ -257,7 +257,7 @@ function main: let inputs = [Value::from_str("2group").unwrap()].into_iter(); // Add the program to the VM. - vm.process().write().add_program(&program, 0).unwrap(); + vm.process().write().add_program(&program).unwrap(); // Create an execution transaction that is 164613 bytes in size. let transaction = vm.execute(&private_key, ("too_big.aleo", "main"), inputs, None, 0, None, rng).unwrap(); diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index a412080102..153ff8c1c4 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -156,12 +156,6 @@ impl Deployment { pub fn to_deployment_id(&self) -> Result> { Ok(*Transaction::deployment_tree(self, None)?.root()) } - - // TODO (@d0cd) Contemplate design. - /// Updates the deployment edition. - pub fn update_edition(&mut self, edition: u16) { - self.edition = edition; - } } #[cfg(test)] diff --git a/ledger/puzzle/epoch/src/synthesis/program/mod.rs b/ledger/puzzle/epoch/src/synthesis/program/mod.rs index f896957fe7..d1a1ac2298 100644 --- a/ledger/puzzle/epoch/src/synthesis/program/mod.rs +++ b/ledger/puzzle/epoch/src/synthesis/program/mod.rs @@ -106,7 +106,7 @@ function synthesize: // Initialize a new process. let process = Process::::load()?; // Initialize the stack with the synthesis challenge program. - let stack = Stack::new(&process, &program, 0)?; + let stack = Stack::new(&process, &program)?; Ok(Self { stack, register_table, epoch_hash }) } diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index cf4ba0dde2..d3aacbd33c 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -2183,7 +2183,7 @@ fn test_deployment_exceeding_max_transaction_spend() { // Attempt to initialize a `Stack` for the program. // If this fails, then by `Stack::initialize` the finalize cost exceeds the `TRANSACTION_SPEND_LIMIT`. - if Stack::::new(&ledger.vm().process().read(), &program, 0).is_err() { + if Stack::::new(&ledger.vm().process().read(), &program).is_err() { exceeding_program = Some(program); break; } else { diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index 0a11096985..1cbc0331d7 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -510,7 +510,6 @@ pub trait DeploymentStorage: Clone + Send + Sync { } /// Returns the deployment for the given `transaction ID`. - // TODO (@d0cd) This should be done with IDMapV2. fn get_deployment(&self, transaction_id: &N::TransactionID) -> Result>> { // Retrieve the program ID and edition. let (program_id, edition) = match self.get_program_id_and_edition(transaction_id)? { @@ -723,6 +722,11 @@ impl> DeploymentStore { self.storage.get_program_id(transaction_id) } + /// Returns the program ID and edition for the given `transaction ID`. + pub fn get_program_id_and_edition(&self, transaction_id: &N::TransactionID) -> Result, u16)>> { + self.storage.get_program_id_and_edition(transaction_id) + } + /// Returns the latest program for the given `program ID`. pub fn get_program(&self, program_id: &ProgramID) -> Result>> { self.storage.get_program(program_id) diff --git a/ledger/store/src/transaction/mod.rs b/ledger/store/src/transaction/mod.rs index 6bac5409ec..3bff966290 100644 --- a/ledger/store/src/transaction/mod.rs +++ b/ledger/store/src/transaction/mod.rs @@ -366,7 +366,11 @@ impl> TransactionStore { // Retrieve the edition. match transaction_type { TransactionType::Deploy => { - todo!("@d0cd Use IDMapV2") + // Return the edition. + match self.storage.deployment_store().get_program_id_and_edition(transaction_id)? { + Some((_, edition)) => Ok(Some(edition)), + None => bail!("Failed to get the edition for deployment transaction '{transaction_id}'"), + } } // Return 'None'. TransactionType::Execute => Ok(None), diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index 4d15d9541b..1ae367b19a 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -401,7 +401,7 @@ pub fn sample_large_execution_transaction(rng: &mut TestRng) -> Transaction::new(&process, program, 0), + |program| Stack::::new(&process, program), BatchSize::PerIteration, ) }); @@ -86,7 +86,7 @@ fn bench_stack_new(c: &mut Criterion) { )) .unwrap() }, - |program| Stack::::new(&process, program, 0), + |program| Stack::::new(&process, program), BatchSize::PerIteration, ) }); @@ -150,7 +150,7 @@ fn add_program_at_depth(process: &mut Process, depth: usize) { }; // Add the program to the process. - process.add_program(&program, 0).unwrap(); + process.add_program(&program).unwrap(); } // Samples a random identifier as a string. diff --git a/synthesizer/process/src/deploy.rs b/synthesizer/process/src/deploy.rs index e8c10aef37..b6787c9cf8 100644 --- a/synthesizer/process/src/deploy.rs +++ b/synthesizer/process/src/deploy.rs @@ -26,8 +26,7 @@ impl Process { let timer = timer!("Process::deploy"); // Compute the stack. - // TODO (@d0cd) Should we be using a zero edition? - let stack = Stack::new(self, program, 0)?; + let stack = Stack::new(self, program)?; lap!(timer, "Compute the stack"); // Return the deployment. @@ -46,9 +45,15 @@ impl Process { let timer = timer!("Process::load_deployment"); // Compute the program stack. - let stack = Stack::new(self, deployment.program(), deployment.edition())?; + let stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); + // Ensure that the computed stack's edition matches the deployment's edition. + ensure!( + stack.edition() == deployment.edition(), + "The computed stack's edition does not match the deployment's edition" + ); + // Insert the verifying keys. for (function_name, (verifying_key, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 3901d01b8a..48699f36d5 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -36,75 +36,39 @@ impl Process { let timer = timer!("Process::finalize_deployment"); // Compute the program stack. - let stack = Stack::new(self, deployment.program(), deployment.edition())?; + let stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); + // Ensure that the computed stack's edition matches the deployment edition. + ensure!( + stack.edition() == deployment.edition(), + "The computed stack's edition does not match the deployment's edition" + ); + // Insert the verifying keys. for (function_name, (verifying_key, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; } lap!(timer, "Insert the verifying keys"); - // Initialize the mappings, and store their finalize operations. - atomic_batch_scope!(store, { - // Initialize a list for the finalize operations. - let mut finalize_operations = Vec::with_capacity(deployment.program().mappings().len()); - - /* Finalize the fee. */ - - // Retrieve the fee stack. - let fee_stack = self.get_stack(fee.program_id())?; - // Finalize the fee transition. - finalize_operations.extend(finalize_fee_transition(state, store, fee_stack, fee)?); - lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name()); - - /* Finalize the deployment. */ - - // Retrieve the program ID. - let program_id = deployment.program_id(); - // Iterate over the mappings. - for mapping in deployment.program().mappings().values() { - // Initialize the mapping. - finalize_operations.push(store.initialize_mapping(*program_id, *mapping.name())?); + // Determine which mappings must be initialized. + let mappings = match deployment.edition().is_zero() { + true => deployment.program().mappings().values().collect::>(), + false => { + // Get the existing stack. + let existing_stack = self.get_stack(deployment.program_id())?; + // Get the existing mappings. + let existing_mappings = existing_stack.program().mappings(); + // Determine and return the new mappings + let mut new_mappings = Vec::new(); + for mapping in deployment.program().mappings().values() { + if !existing_mappings.contains_key(mapping.name()) { + new_mappings.push(mapping); + } + } + new_mappings } - finish!(timer, "Initialize the program mappings"); - - // Return the stack and finalize operations. - Ok((stack, finalize_operations)) - }) - } - - /// Finalizes the deployment and fee. - // TODO (@d0cd) We should specify what valid here means, because `Stack::new` performs a number of checks - /// This method assumes the given deployment **is valid**. - /// This method should **only** be called by `VM::finalize()`. - #[inline] - pub fn finalize_update>( - &self, - state: FinalizeGlobalState, - store: &FinalizeStore, - deployment: &Deployment, - fee: &Fee, - ) -> Result<(Stack, Vec>)> { - let timer = timer!("Process::finalize_deployment"); - - // Get the existing stack. - let old_stack = self.get_stack(deployment.program_id())?; - - // Check that the update is valid. - old_stack.check_update(deployment.program())?; - - // Compute the program stack. - let stack = Stack::new(self, deployment.program(), deployment.edition())?; - lap!(timer, "Update the stack"); - - // TODO (@d0cd) Add the mappings. - - // Insert the verifying keys. - for (function_name, (verifying_key, _)) in deployment.verifying_keys() { - stack.insert_verifying_key(function_name, verifying_key.clone())?; - } - lap!(timer, "Insert the verifying keys"); + }; // Initialize the mappings, and store their finalize operations. atomic_batch_scope!(store, { @@ -123,8 +87,8 @@ impl Process { // Retrieve the program ID. let program_id = deployment.program_id(); - // Iterate over the mappings. - for mapping in deployment.program().mappings().values() { + // Iterate over the mappings that must be initialized. + for mapping in mappings { // Initialize the mapping. finalize_operations.push(store.initialize_mapping(*program_id, *mapping.name())?); } diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index 1efd72d765..473ca9f57e 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -97,9 +97,12 @@ impl Process { lap!(timer, "Load credits program"); // Compute the 'credits.aleo' program stack. - let stack = Stack::new(&process, &program, 0)?; + let stack = Stack::new(&process, &program)?; lap!(timer, "Initialize stack"); + // Check that the edition is zero. + ensure!(stack.edition() == 0, "The 'credits.aleo' program must have edition 0"); + // Synthesize the 'credits.aleo' circuit keys. for function_name in program.functions().keys() { stack.synthesize_key::(function_name, rng)?; @@ -118,12 +121,12 @@ impl Process { /// Adds a new program to the process. /// If you intend to `execute` the program, use `deploy` and `finalize_deployment` instead. #[inline] - pub fn add_program(&mut self, program: &Program, edition: u16) -> Result<()> { + pub fn add_program(&mut self, program: &Program) -> Result<()> { // Initialize the 'credits.aleo' program ID. let credits_program_id = ProgramID::::from_str("credits.aleo")?; // If the program is not 'credits.aleo', compute the program stack, and add it to the process. if program.id() != &credits_program_id { - self.add_stack(Stack::new(self, program, edition)?)?; + self.add_stack(Stack::new(self, program)?)?; } Ok(()) } @@ -158,9 +161,12 @@ impl Process { lap!(timer, "Load credits program"); // Compute the 'credits.aleo' program stack. - let stack = Stack::new(&process, &program, 0)?; + let stack = Stack::new(&process, &program)?; lap!(timer, "Initialize stack"); + // Check that the edition is zero. + ensure!(stack.edition() == 0, "The 'credits.aleo' program must have edition 0"); + // Synthesize the 'credits.aleo' verifying keys. for function_name in program.functions().keys() { // Load the verifying key. @@ -194,7 +200,10 @@ impl Process { let program = Program::credits()?; // Compute the 'credits.aleo' program stack. - let stack = Stack::new(&process, &program, 0)?; + let stack = Stack::new(&process, &program)?; + + // Check that the edition is zero. + ensure!(stack.edition() == 0, "The 'credits.aleo' program must have edition 0"); // Add the stack to the process. process.add_stack(stack)?; @@ -324,7 +333,7 @@ pub mod test_helpers { // Add the program to the process if doesn't yet exist. if !process.contains_program(program.id()) { - process.add_program(program, 0).unwrap(); + process.add_program(program).unwrap(); } // Compute the authorization. @@ -458,7 +467,7 @@ function compute: // Construct a new process. let mut process = Process::load().unwrap(); // Add the program to the process. - process.add_program(program, 0).unwrap(); + process.add_program(program).unwrap(); // Return the process. process } diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 19de63e11b..8712dcf257 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -51,7 +51,7 @@ impl Stack { finish!(timer); // Return the deployment. - Deployment::new(N::EDITION, self.program.clone(), verifying_keys) + Deployment::new(self.edition, self.program.clone(), verifying_keys) } /// Checks each function in the program on the given verifying key and certificate. diff --git a/synthesizer/process/src/stack/helpers/check_update.rs b/synthesizer/process/src/stack/helpers/check_update.rs index 7c69c86683..83f7b71791 100644 --- a/synthesizer/process/src/stack/helpers/check_update.rs +++ b/synthesizer/process/src/stack/helpers/check_update.rs @@ -18,9 +18,11 @@ use super::*; impl Stack { /// Updates an existing stack, given the process and program. #[inline] - pub(crate) fn check_update(&self, program: &Program) -> Result<()> { + pub(crate) fn check_update(process: &Process, program: &Program) -> Result<()> { + // Get the existing stack. + let stack = process.get_stack(program.id())?; // Get the old program. - let old_program = self.program(); + let old_program = stack.program(); // Ensure the program ID matches. ensure!(old_program.id() == program.id(), "Cannot update program with different program ID"); diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 39a1e2511b..f5436554c6 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -43,8 +43,8 @@ impl Stack { } // Retrieve the external stack for the import program ID. let external_stack = process.get_stack(import)?; - // Add the external stack to the stack. // TODO (@d0cd): Handle bookkeeping here. + // TODO (@d0cd). Program depth is now useless. // Update the program depth, checking that it does not exceed the maximum call depth. stack.program_depth = std::cmp::max(stack.program_depth, external_stack.program_depth() + 1); ensure!( diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index a20e6d0628..24ae3ec0de 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -200,16 +200,26 @@ pub struct Stack { } impl Stack { - /// Initializes a new stack, if it does not already exist, given the process and the program. + /// Initializes a new stack given the process and the program. #[inline] - pub fn new(process: &Process, program: &Program, edition: u16) -> Result { + pub fn new(process: &Process, program: &Program) -> Result { // Retrieve the program ID. let program_id = program.id(); - // Ensure the program does not already exist in the process. - ensure!(!process.contains_program(program_id), "Program '{program_id}' already exists"); + // Determine the program edition. + let edition = match process.get_stack(program_id) { + // If the program does not exist, then the edition is zero. + Err(_) => 0, + // Otherwise, check that the update is valid, then retrieve the old program edition and increment it. + Ok(stack) => { + Stack::check_update(process, program)?; + match stack.edition.checked_add(1) { + Some(edition) => edition, + None => bail!("The program cannot be updated past the current edition `{}`", stack.edition), + } + } + }; // Ensure the program contains functions. ensure!(!program.functions().is_empty(), "No functions present in the deployment for program '{program_id}'"); - // Serialize the program into bytes. let program_bytes = program.to_bytes_le()?; // Ensure the program deserializes from bytes correctly. @@ -259,7 +269,8 @@ impl StackProgram for Stack { /// Returns the external stack for the given program ID. #[inline] fn get_external_stack(&self, program_id: &ProgramID) -> Result>> { - // TODO (@d0cd), Check that stack is in imports + // Check that the program ID is imported by the program. + ensure!(self.program.contains_import(program_id), "External program '{program_id}' is not imported."); // Upgrade the reference to the global stack map. let stacks = self.stacks.upgrade().ok_or_else(|| anyhow!("Global stack map does not exist."))?; // Retrieve the stack. diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index fd346f3a60..a2311f57ca 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -1103,7 +1103,7 @@ function transfer: assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program1, 0).unwrap(); + process.add_program(&program1).unwrap(); // Initialize the RNG. let rng = &mut TestRng::default(); @@ -1868,7 +1868,7 @@ function b: assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program1, 0).unwrap(); + process.add_program(&program1).unwrap(); // Initialize another program. let (string, program2) = Program::::parse( @@ -1887,7 +1887,7 @@ function a: assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program2, 0).unwrap(); + process.add_program(&program2).unwrap(); // Initialize the RNG. let rng = &mut TestRng::default(); @@ -2004,7 +2004,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program1, 0).unwrap(); + process.add_program(&program1).unwrap(); // Initialize another program. let (string, program2) = Program::::parse( @@ -2025,7 +2025,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program2, 0).unwrap(); + process.add_program(&program2).unwrap(); // Initialize another program. let (string, program3) = Program::::parse( @@ -2048,7 +2048,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program3, 0).unwrap(); + process.add_program(&program3).unwrap(); // Initialize another program. let (string, program4) = Program::::parse( @@ -2069,7 +2069,7 @@ fn test_complex_execution_order() { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Add the program to the process. - process.add_program(&program4, 0).unwrap(); + process.add_program(&program4).unwrap(); // Initialize the RNG. let rng = &mut TestRng::default(); @@ -2515,7 +2515,7 @@ fn test_long_import_chain() { )) .unwrap(); // Add the program to the process. - process.add_program(&program, 0).unwrap(); + process.add_program(&program).unwrap(); } // Add the `MAX_PROGRAM_DEPTH + 1` program to the process, which should fail. @@ -2528,7 +2528,7 @@ fn test_long_import_chain() { CurrentNetwork::MAX_PROGRAM_DEPTH + 1 )) .unwrap(); - let result = process.add_program(&program, 0); + let result = process.add_program(&program); assert!(result.is_err()); } @@ -2561,7 +2561,7 @@ fn test_long_import_chain_with_calls() { )) .unwrap(); // Add the program to the process. - process.add_program(&program, 0).unwrap(); + process.add_program(&program).unwrap(); // Check that the number of calls is correct. let stack = process.get_stack(program.id()).unwrap(); let number_of_calls = stack.get_number_of_calls(program.functions().into_iter().next().unwrap().0).unwrap(); @@ -2580,7 +2580,7 @@ fn test_long_import_chain_with_calls() { Transaction::::MAX_TRANSITIONS - 2 )) .unwrap(); - let result = process.add_program(&program, 0); + let result = process.add_program(&program); assert!(result.is_err()) } @@ -2595,7 +2595,7 @@ fn test_max_imports() { // Initialize a new program. let program = Program::from_str(&format!("program test{i}.aleo; function c:")).unwrap(); // Add the program to the process. - process.add_program(&program, 0).unwrap(); + process.add_program(&program).unwrap(); } // Add a program importing all `MAX_IMPORTS` programs, which should pass. @@ -2604,7 +2604,7 @@ fn test_max_imports() { let program = Program::from_str(&format!("{import_string}program test{}.aleo; function c:", CurrentNetwork::MAX_IMPORTS)) .unwrap(); - process.add_program(&program, 0).unwrap(); + process.add_program(&program).unwrap(); // Attempt to construct a program importing `MAX_IMPORTS + 1` programs, which should fail. let import_string = @@ -2641,7 +2641,7 @@ fn test_program_exceeding_transaction_spend_limit() { let mut process = Process::::load().unwrap(); // Attempt to add the program to the process, which should fail. - let result = process.add_program(&program, 0); + let result = process.add_program(&program); assert!(result.is_err()); // Attempt to initialize a `Stack` directly with the program, which should fail. diff --git a/synthesizer/process/src/verify_deployment.rs b/synthesizer/process/src/verify_deployment.rs index b14a41122d..a4c83bebe2 100644 --- a/synthesizer/process/src/verify_deployment.rs +++ b/synthesizer/process/src/verify_deployment.rs @@ -31,9 +31,15 @@ impl Process { ensure!(!self.contains_program(program_id), "Program '{program_id}' already exists"); // Ensure the program is well-formed, by computing the stack. - let stack = Stack::new(self, deployment.program(), deployment.edition())?; + let stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); + // Check that the computed stack's edition matches the deployment edition. + ensure!( + stack.edition() == deployment.edition(), + "The computed stack's edition does not match the deployment's edition" + ); + // Ensure the verifying keys are well-formed and the certificates are valid. let verification = stack.verify_deployment::(deployment, rng); lap!(timer, "Verify the deployment"); diff --git a/synthesizer/program/tests/instruction/assert.rs b/synthesizer/program/tests/instruction/assert.rs index 519fa838f7..6c06918b23 100644 --- a/synthesizer/program/tests/instruction/assert.rs +++ b/synthesizer/program/tests/instruction/assert.rs @@ -72,7 +72,7 @@ fn sample_stack( let operands = vec![operand_a, operand_b]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program, 0)?; + let stack = Stack::new(&Process::load()?, &program)?; Ok((stack, operands)) } diff --git a/synthesizer/program/tests/instruction/commit.rs b/synthesizer/program/tests/instruction/commit.rs index 37bbee2787..c22f362f11 100644 --- a/synthesizer/program/tests/instruction/commit.rs +++ b/synthesizer/program/tests/instruction/commit.rs @@ -93,7 +93,7 @@ fn sample_stack( let operands = vec![operand_a, operand_b]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program, 0)?; + let stack = Stack::new(&Process::load()?, &program)?; Ok((stack, operands, r2)) } diff --git a/synthesizer/program/tests/instruction/hash.rs b/synthesizer/program/tests/instruction/hash.rs index 3f4e185a9e..106474e978 100644 --- a/synthesizer/program/tests/instruction/hash.rs +++ b/synthesizer/program/tests/instruction/hash.rs @@ -109,7 +109,7 @@ fn sample_stack( let operands = vec![Operand::Register(r0)]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program, 0)?; + let stack = Stack::new(&Process::load()?, &program)?; Ok((stack, operands, r1)) } diff --git a/synthesizer/program/tests/instruction/is.rs b/synthesizer/program/tests/instruction/is.rs index 51f2e84363..1d58f84cdd 100644 --- a/synthesizer/program/tests/instruction/is.rs +++ b/synthesizer/program/tests/instruction/is.rs @@ -83,7 +83,7 @@ fn sample_stack( let operands = vec![operand_a, operand_b]; // Initialize the stack. - let stack = Stack::new(&Process::load()?, &program, 0)?; + let stack = Stack::new(&Process::load()?, &program)?; Ok((stack, operands, r2)) } diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index 64c4358bfe..efc92412be 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -76,8 +76,7 @@ impl> VM { /// /// The `priority_fee_in_microcredits` is an additional fee **on top** of the deployment fee. #[allow(clippy::too_many_arguments)] - // TODO (@d0cd) Better name. - pub fn deploy_with_authority_and_edition( + pub fn deploy_updatable( &self, private_key: &PrivateKey, authority: Address, @@ -89,9 +88,14 @@ impl> VM { rng: &mut R, ) -> Result> { // Compute the deployment. - let mut deployment = self.deploy_raw(program, rng)?; - // Update the edition. - deployment.update_edition(*edition); + let deployment = self.deploy_raw(program, rng)?; + // Ensure that the deployment edition matches the expected edition. + ensure!( + deployment.edition() == *edition, + "The deployment edition ({}) does not match the expected edition ({})", + deployment.edition(), + *edition + ); // Ensure the transaction is not empty. ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty transaction deployment"); // Compute the deployment ID. diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index ee6a3c8539..7e0ed3b170 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -365,66 +365,28 @@ impl> VM { } }, // If the program has not yet been deployed, attempt to deploy it. - false => { - // Handle initial deployments and updates via `finalize_deployment` and `finalize_update` respectively. - // TODO (@d0cd) Dedup logic - let is_initial_deployment = deployment.edition() == 0; - if is_initial_deployment { - match process.finalize_deployment(state, store, deployment, fee) { - // Construct the accepted deploy transaction. - Ok((_, finalize)) => { - // Add the program id to the list of deployments. - deployments.insert(*deployment.program_id()); - ConfirmedTransaction::accepted_deploy( - counter, - transaction.clone(), - finalize, - ) - .map_err(|e| e.to_string()) - } - // Construct the rejected deploy transaction. - Err(_error) => match process_rejected_deployment(fee, *deployment.clone()) { - Ok(result) => result, - Err(error) => { - // Note: On failure, skip this transaction, and continue speculation. - #[cfg(debug_assertions)] - eprintln!("Failed to finalize the fee in a rejected deploy - {error}"); - // Store the aborted transaction. - aborted.push((transaction.clone(), error.to_string())); - // Continue to the next transaction. - continue 'outer; - } - }, - } - } else { - match process.finalize_update(state, store, deployment, fee) { - // Construct the accepted deploy transaction. - Ok((_, finalize)) => { - // Add the program id to the list of deployments. - deployments.insert(*deployment.program_id()); - ConfirmedTransaction::accepted_deploy( - counter, - transaction.clone(), - finalize, - ) - .map_err(|e| e.to_string()) - } - // Construct the rejected deploy transaction. - Err(_error) => match process_rejected_deployment(fee, *deployment.clone()) { - Ok(result) => result, - Err(error) => { - // Note: On failure, skip this transaction, and continue speculation. - #[cfg(debug_assertions)] - eprintln!("Failed to finalize the fee in a rejected deploy - {error}"); - // Store the aborted transaction. - aborted.push((transaction.clone(), error.to_string())); - // Continue to the next transaction. - continue 'outer; - } - }, - } + false => match process.finalize_deployment(state, store, deployment, fee) { + // Construct the accepted deploy transaction. + Ok((_, finalize)) => { + // Add the program id to the list of deployments. + deployments.insert(*deployment.program_id()); + ConfirmedTransaction::accepted_deploy(counter, transaction.clone(), finalize) + .map_err(|e| e.to_string()) } - } + // Construct the rejected deploy transaction. + Err(_error) => match process_rejected_deployment(fee, *deployment.clone()) { + Ok(result) => result, + Err(error) => { + // Note: On failure, skip this transaction, and continue speculation. + #[cfg(debug_assertions)] + eprintln!("Failed to finalize the fee in a rejected deploy - {error}"); + // Store the aborted transaction. + aborted.push((transaction.clone(), error.to_string())); + // Continue to the next transaction. + continue 'outer; + } + }, + }, } } // The finalize operation here involves calling 'update_key_value', @@ -652,48 +614,24 @@ impl> VM { // Note: This will abort the entire atomic batch. _ => return Err("Expected deploy transaction".to_string()), }; - // Handle initial deployments and updates via `finalize_deployment` and `finalize_update` respectively. - // TODO (@d0cd) Dedup logic - let is_initial_deployment = deployment.edition() == 0; - if is_initial_deployment { - // The finalize operation here involves appending the 'stack', and adding the program to the finalize tree. - match process.finalize_deployment(state, store, deployment, fee) { - // Ensure the finalize operations match the expected. - Ok((stack, finalize_operations)) => match finalize == &finalize_operations { - // Store the stack. - true => stacks.push(stack), - // Note: This will abort the entire atomic batch. - false => { - return Err(format!( - "Mismatch in finalize operations for an accepted deploy - (found: {finalize_operations:?}, expected: {finalize:?})" - )); - } - }, - // Note: This will abort the entire atomic batch. - Err(error) => { - return Err(format!("Failed to finalize an accepted deploy transaction - {error}")); - } - }; - } else { - // The finalize operation here involves updating the 'stack', and updating the program in the finalize tree. - match process.finalize_update(state, store, deployment, fee) { - // Ensure the finalize operations match the expected. - Ok((stack, finalize_operations)) => match finalize == &finalize_operations { - // Store the stack. - true => stacks.push(stack), - // Note: This will abort the entire atomic batch. - false => { - return Err(format!( - "Mismatch in finalize operations for an accepted deploy - (found: {finalize_operations:?}, expected: {finalize:?})" - )); - } - }, + // The finalize operation here involves appending the 'stack', and adding the program to the finalize tree. + match process.finalize_deployment(state, store, deployment, fee) { + // Ensure the finalize operations match the expected. + Ok((stack, finalize_operations)) => match finalize == &finalize_operations { + // Store the stack. + true => stacks.push(stack), // Note: This will abort the entire atomic batch. - Err(error) => { - return Err(format!("Failed to finalize an accepted deploy transaction - {error}")); + false => { + return Err(format!( + "Mismatch in finalize operations for an accepted deploy - (found: {finalize_operations:?}, expected: {finalize:?})" + )); } - }; - } + }, + // Note: This will abort the entire atomic batch. + Err(error) => { + return Err(format!("Failed to finalize an accepted deploy transaction - {error}")); + } + }; Ok(()) } diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 5af5d83124..aed27fb2bc 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -180,11 +180,8 @@ impl> VM { for (program_id, deployment) in deployments.iter().flatten() { // Load the deployment if it does not exist in the process yet. - // Otherwise, update the existing program with the new deployment. if !process.contains_program(program_id) { process.load_deployment(deployment)?; - } else { - todo!("@d0cd") } } } diff --git a/synthesizer/tests/test_process_execute.rs b/synthesizer/tests/test_process_execute.rs index caaedbfcec..8d63e707fd 100644 --- a/synthesizer/tests/test_process_execute.rs +++ b/synthesizer/tests/test_process_execute.rs @@ -57,7 +57,7 @@ fn run_test(process: Process, test: &ProgramTest) -> serde_yaml: // Add the programs into the process. let mut process = process.clone(); for program in test.programs() { - if let Err(err) = process.add_program(program, 0) { + if let Err(err) = process.add_program(program) { output .get_mut(serde_yaml::Value::String("errors".to_string())) .unwrap() diff --git a/vm/file/prover.rs b/vm/file/prover.rs index 864cbe37b1..1bac807b46 100644 --- a/vm/file/prover.rs +++ b/vm/file/prover.rs @@ -230,7 +230,7 @@ function compute: // Construct the process. let mut process = Process::load().unwrap(); // Add the program to the process. - process.add_program(&program, 0).unwrap(); + process.add_program(&program).unwrap(); // Prepare the function name. let function_name = Identifier::from_str("compute").unwrap(); diff --git a/vm/file/verifier.rs b/vm/file/verifier.rs index b55a59f54d..af0b4f9673 100644 --- a/vm/file/verifier.rs +++ b/vm/file/verifier.rs @@ -230,7 +230,7 @@ function compute: // Construct the process. let mut process = Process::load().unwrap(); // Add the program to the process. - process.add_program(&program, 0).unwrap(); + process.add_program(&program).unwrap(); // Prepare the function name. let function_name = Identifier::from_str("compute").unwrap(); diff --git a/vm/package/deploy.rs b/vm/package/deploy.rs index bebc12b7cf..f423ed51de 100644 --- a/vm/package/deploy.rs +++ b/vm/package/deploy.rs @@ -137,7 +137,7 @@ impl Package { // Open the Aleo program file. let import_program_file = AleoFile::open(&imports_directory, program_id, false)?; // Add the import program. - process.add_program(import_program_file.program(), 0)?; + process.add_program(import_program_file.program())?; Ok::<_, Error>(()) })?; diff --git a/vm/package/mod.rs b/vm/package/mod.rs index 55b7752f95..678d892400 100644 --- a/vm/package/mod.rs +++ b/vm/package/mod.rs @@ -166,13 +166,13 @@ impl Package { // Open the Aleo program file. let import_program_file = AleoFile::open(&imports_directory, program_id, false)?; // Add the import program. - process.add_program(import_program_file.program(), 0)?; + process.add_program(import_program_file.program())?; } Ok::<_, Error>(()) })?; // Add the program to the process. - process.add_program(self.program(), 0)?; + process.add_program(self.program())?; Ok(process) } From 417e37398a14dd31a53207a3f8555228288d2a09 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:13:19 -0800 Subject: [PATCH 15/46] Clean up check_update --- synthesizer/process/src/cost.rs | 2 +- .../src/stack/finalize_types/initialize.rs | 8 +++- .../process/src/stack/helpers/check_update.rs | 47 +++++++++---------- .../process/src/stack/helpers/initialize.rs | 8 +--- synthesizer/process/src/stack/mod.rs | 4 +- 5 files changed, 32 insertions(+), 37 deletions(-) diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 5d805d0989..2284268619 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -431,7 +431,7 @@ pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Ide }) } -/// Returns the minimum number of microcredits required to run the finalize (depcrated). +/// Returns the minimum number of microcredits required to run the finalize (deprecated). pub fn cost_in_microcredits_v1(stack: &Stack, function_name: &Identifier) -> Result { // Retrieve the finalize logic. let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else { diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index 80f5d7db52..fb12274cc1 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -144,7 +144,13 @@ impl FinalizeTypes { RegisterTypes::check_struct(stack, struct_name)? } FinalizeType::Plaintext(PlaintextType::Array(array_type)) => RegisterTypes::check_array(stack, array_type)?, - FinalizeType::Future(..) => (), + FinalizeType::Future(locator) => { + ensure!( + stack.program().contains_import(locator.program_id()), + "Program '{locator}' is not imported by '{}'.", + stack.program().id() + ) + }, }; // Insert the input register. diff --git a/synthesizer/process/src/stack/helpers/check_update.rs b/synthesizer/process/src/stack/helpers/check_update.rs index 83f7b71791..91412d2655 100644 --- a/synthesizer/process/src/stack/helpers/check_update.rs +++ b/synthesizer/process/src/stack/helpers/check_update.rs @@ -16,22 +16,22 @@ use super::*; impl Stack { - /// Updates an existing stack, given the process and program. + /// Checks that the new program definition is a valid update. #[inline] - pub(crate) fn check_update(process: &Process, program: &Program) -> Result<()> { - // Get the existing stack. - let stack = process.get_stack(program.id())?; + pub(crate) fn check_update_is_valid(process: &Process, program: &Program) -> Result<()> { + // Get the new program ID. + let program_id = program.id(); // Get the old program. - let old_program = stack.program(); + let old_program = process.get_stack(program.id())?.program(); // Ensure the program ID matches. - ensure!(old_program.id() == program.id(), "Cannot update program with different program ID"); + ensure!(old_program.id() == program.id(), "Cannot update '{program_id}' with different program ID"); // Ensure that all of the structs in the old program exist in the new program. for (struct_id, struct_type) in old_program.structs() { let new_struct_type = program.get_struct(struct_id)?; ensure!( struct_type == new_struct_type, - "Cannot update program because the struct '{struct_id}' has different types" + "Cannot update '{program_id}' because the struct '{struct_id}' does not match" ); } // Ensure that all of the records in the old program exist in the new program. @@ -39,7 +39,7 @@ impl Stack { let new_record_type = program.get_record(record_id)?; ensure!( record_type == new_record_type, - "Cannot update program because the record '{record_id}' has different types" + "Cannot update '{program_id}' because the record '{record_id}' does not match" ); } // Ensure that all of the mappings in the old program exist in the new program. @@ -47,55 +47,50 @@ impl Stack { let new_mapping_type = program.get_mapping(mapping_id)?; ensure!( *mapping_type == new_mapping_type, - "Cannot update program because the mapping '{mapping_id}' has different types" + "Cannot update '{program_id}' because the mapping '{mapping_id}' does not match" ); } // Ensure that all of the imports in the old program exist in the new program. for import in old_program.imports().keys() { if !program.contains_import(import) { - bail!("Cannot update program because it is missing the import '{import}'"); + bail!("Cannot update '{program_id}' because it is missing the original import '{import}'"); } } - // Ensure that the old program closures exist in the new program, with the same input and output types. + // Ensure that the old program closures exist in the new program, with the exact same definition for closure in old_program.closures().values() { - if !program.contains_closure(closure.name()) { - bail!("Cannot update program because it is missing the closure '{closure}'"); - } - let new_closure = program.get_closure(closure.name())?; - ensure!( - closure.inputs() == new_closure.inputs(), - "Cannot update program because the closure '{closure}' has different input types" - ); + let closure_name = closure.name(); + let new_closure = program.get_closure(closure_name)?; ensure!( - closure.outputs() == new_closure.outputs(), - "Cannot update program because the closure '{closure}' has different output types" + closure == &new_closure, + "Cannot update '{program_id}' because the closure '{closure_name}' does not exactly match" ); } // Ensure that the old program functions exist in the new program, with the same input and output types. // If the function has an associated `finalize` block, then ensure that the finalize block exists in the new program. for function in old_program.functions().values() { + let function_name = function.name(); if !program.contains_function(function.name()) { - bail!("Cannot update program because it is missing the function '{function}'"); + bail!("Cannot update '{program_id}' because it is missing the function '{function_name}'"); } let new_function = program.get_function(function.name())?; ensure!( function.inputs() == new_function.inputs(), - "Cannot update program because the function '{function}' has different input types" + "Cannot update '{program_id}' because the inputs to the function '{function_name}' do not match" ); ensure!( function.outputs() == new_function.outputs(), - "Cannot update program because the function '{function}' has different output types" + "Cannot update '{program_id}' because the outputs of the function '{function_name}' do not match" ); if let Some(finalize) = function.finalize_logic() { match new_function.finalize_logic() { Some(new_finalize) => { ensure!( finalize.inputs() == new_finalize.inputs(), - "Cannot update program because the finalize block '{finalize}' has different input types" + "Cannot update '{program_id}' because the finalize inputs to the function '{function_name}' do not match" ); } None => { - bail!("Cannot update program because the function '{function}' is missing a finalize block") + bail!("Cannot update '{program_id}' because the function '{function_name}' is missing a finalize block") } } } diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index f5436554c6..a9fa455efe 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -29,7 +29,6 @@ impl Stack { proving_keys: Default::default(), verifying_keys: Default::default(), number_of_calls: Default::default(), - finalize_costs: Default::default(), program_depth: 0, program_address: program.id().to_address()?, edition, @@ -69,12 +68,7 @@ impl Stack { // Determine if this is a function call. if call.is_function_call(&stack)? { // Increment by the number of calls. - num_calls += match call.operator() { - CallOperator::Locator(locator) => stack - .get_external_stack(locator.program_id())? - .get_number_of_calls(locator.resource())?, - CallOperator::Resource(resource) => stack.get_number_of_calls(resource)?, - }; + num_calls += 1 } } } diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 24ae3ec0de..1075db428d 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -187,7 +187,7 @@ pub struct Stack { proving_keys: Arc, ProvingKey>>>, /// The mapping of function name to verifying key. verifying_keys: Arc, VerifyingKey>>>, - /// The mapping of function names to the number of calls. + /// The mapping of function names to the number of function calls. number_of_calls: IndexMap, usize>, /// The mapping of function names to finalize cost. finalize_costs: IndexMap, u64>, @@ -211,7 +211,7 @@ impl Stack { Err(_) => 0, // Otherwise, check that the update is valid, then retrieve the old program edition and increment it. Ok(stack) => { - Stack::check_update(process, program)?; + Stack::check_update_is_valid(process, program)?; match stack.edition.checked_add(1) { Some(edition) => edition, None => bail!("The program cannot be updated past the current edition `{}`", stack.edition), From 812a210aff382aeec2797cbc550c310a4b37aee0 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:36:46 -0800 Subject: [PATCH 16/46] Remove caching of finalize costs as they are invalid on an update to the finalize logic --- synthesizer/process/src/cost.rs | 5 +++-- .../src/stack/finalize_types/initialize.rs | 2 +- .../process/src/stack/helpers/check_update.rs | 4 +++- .../process/src/stack/helpers/initialize.rs | 11 ----------- synthesizer/process/src/stack/mod.rs | 15 ++------------- .../program/src/traits/stack_and_registers.rs | 3 --- synthesizer/src/vm/verify.rs | 15 ++++++++++----- 7 files changed, 19 insertions(+), 36 deletions(-) diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 2284268619..40d0a5883a 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -67,7 +67,8 @@ pub fn execution_cost_v2(process: &Process, execution: &Execution let transition = execution.peek()?; // Get the finalize cost for the root transition. - let finalize_cost = process.get_stack(transition.program_id())?.get_finalize_cost(transition.function_name())?; + let stack = process.get_stack(transition.program_id())?; + let finalize_cost = cost_in_microcredits_v2(stack, transition.function_name())?; // Compute the total cost in microcredits. let total_cost = storage_cost @@ -417,7 +418,7 @@ pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Ide let stack = stack.get_external_stack(future.program_id())?; // Accumulate the finalize cost of the future. future_cost = future_cost - .checked_add(stack.get_finalize_cost(future.resource())?) + .checked_add(cost_in_microcredits_v2(&stack, future.resource())?) .ok_or(anyhow!("Finalize cost overflowed"))?; } } diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index fb12274cc1..80e4566a9e 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -150,7 +150,7 @@ impl FinalizeTypes { "Program '{locator}' is not imported by '{}'.", stack.program().id() ) - }, + } }; // Insert the input register. diff --git a/synthesizer/process/src/stack/helpers/check_update.rs b/synthesizer/process/src/stack/helpers/check_update.rs index 91412d2655..28bd548c48 100644 --- a/synthesizer/process/src/stack/helpers/check_update.rs +++ b/synthesizer/process/src/stack/helpers/check_update.rs @@ -90,7 +90,9 @@ impl Stack { ); } None => { - bail!("Cannot update '{program_id}' because the function '{function_name}' is missing a finalize block") + bail!( + "Cannot update '{program_id}' because the function '{function_name}' is missing a finalize block" + ) } } } diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index a9fa455efe..d1da38f515 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -80,17 +80,6 @@ impl Stack { ); // Add the number of calls to the stack. stack.number_of_calls.insert(*function.name(), num_calls); - - // Get the finalize cost. - let finalize_cost = cost_in_microcredits_v2(&stack, function.name())?; - // Check that the finalize cost does not exceed the maximum. - ensure!( - finalize_cost <= N::TRANSACTION_SPEND_LIMIT, - "Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'", - function.name(), - N::TRANSACTION_SPEND_LIMIT - ); - stack.finalize_costs.insert(*function.name(), finalize_cost); } // Return the stack. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 1075db428d..b15f159012 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -37,7 +37,7 @@ mod evaluate; mod execute; mod helpers; -use crate::{CallMetrics, Process, Trace, cost_in_microcredits_v2, traits::*}; +use crate::{CallMetrics, Process, Trace, traits::*}; use console::{ account::{Address, PrivateKey}, network::prelude::*, @@ -65,7 +65,7 @@ use console::{ types::{Field, Group}, }; use ledger_block::{Deployment, Transition}; -use synthesizer_program::{CallOperator, Closure, Function, Instruction, Operand, Program, traits::*}; +use synthesizer_program::{Closure, Function, Instruction, Operand, Program, traits::*}; use synthesizer_snark::{Certificate, ProvingKey, UniversalSRS, VerifyingKey}; use aleo_std::prelude::{finish, lap, timer}; @@ -189,8 +189,6 @@ pub struct Stack { verifying_keys: Arc, VerifyingKey>>>, /// The mapping of function names to the number of function calls. number_of_calls: IndexMap, usize>, - /// The mapping of function names to finalize cost. - finalize_costs: IndexMap, u64>, /// The program depth. program_depth: usize, /// The program address. @@ -277,15 +275,6 @@ impl StackProgram for Stack { stacks.get(program_id).cloned().ok_or_else(|| anyhow!("External program '{program_id}' does not exist.")) } - /// Returns the expected finalize cost for the given function name. - #[inline] - fn get_finalize_cost(&self, function_name: &Identifier) -> Result { - self.finalize_costs - .get(function_name) - .copied() - .ok_or_else(|| anyhow!("Function '{function_name}' does not exist")) - } - /// Returns the function with the given function name. #[inline] fn get_function(&self, function_name: &Identifier) -> Result> { diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index 4db069cb79..9b8937c24a 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -77,9 +77,6 @@ pub trait StackProgram { /// Returns the external stack for the given program ID. fn get_external_stack(&self, program_id: &ProgramID) -> Result>; - /// Returns the expected finalize cost for the given function name. - fn get_finalize_cost(&self, function_name: &Identifier) -> Result; - /// Returns the function with the given function name. fn get_function(&self, function_name: &Identifier) -> Result>; diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index dcd6a71849..f78416748b 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -318,12 +318,17 @@ impl> VM { true => execution_cost_v1(&self.process().read(), execution)?, false => execution_cost_v2(&self.process().read(), execution)?, }; + // Ensure the cost does not exceed the transaction spend limit. + ensure!( + cost <= N::TRANSACTION_SPEND_LIMIT, + "Transaction '{id}' exceeds the transaction spend limit '{}'", + N::TRANSACTION_SPEND_LIMIT + ); // Ensure the fee is sufficient to cover the cost. - if *fee.base_amount()? < cost { - bail!( - "Transaction '{id}' has an insufficient base fee (execution) - requires {cost} microcredits" - ) - } + ensure!( + cost <= *fee.base_amount()?, + "Transaction '{id}' has an insufficient base fee (execution) - requires {cost} microcredits" + ); } else { // Ensure the base fee amount is zero. ensure!(*fee.base_amount()? == 0, "Transaction '{id}' has a non-zero base fee (execution)"); From b3a6cedc55c48e82c33ef16ccbf3cf8e69d1210b Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:04:19 -0800 Subject: [PATCH 17/46] Add runtime checks for program costs and and number of calls --- .../process/src/stack/helpers/initialize.rs | 15 +++------ synthesizer/process/src/stack/mod.rs | 33 +++++++++++-------- synthesizer/process/src/verify_execution.rs | 6 ++++ .../program/src/traits/stack_and_registers.rs | 3 -- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index d1da38f515..d04920c071 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -15,6 +15,9 @@ use super::*; +// TODO: @d0cd. Check the MAX_PROGRAM_DEPTH at runtime? +// TODO: @d0cd. Recursion safety for the number of calls and costs at runtime? + impl Stack { /// Initializes a new stack, given the process and program. #[inline] @@ -29,7 +32,6 @@ impl Stack { proving_keys: Default::default(), verifying_keys: Default::default(), number_of_calls: Default::default(), - program_depth: 0, program_address: program.id().to_address()?, edition, }; @@ -40,17 +42,8 @@ impl Stack { if !process.contains_program(import) { bail!("Cannot add program '{}' because its import '{import}' must be added first", program.id()) } - // Retrieve the external stack for the import program ID. - let external_stack = process.get_stack(import)?; - // TODO (@d0cd): Handle bookkeeping here. - // TODO (@d0cd). Program depth is now useless. - // Update the program depth, checking that it does not exceed the maximum call depth. - stack.program_depth = std::cmp::max(stack.program_depth, external_stack.program_depth() + 1); - ensure!( - stack.program_depth <= N::MAX_PROGRAM_DEPTH, - "Program depth exceeds the maximum allowed call depth" - ); } + // Add the program closures to the stack. for closure in program.closures().values() { // Add the closure to the stack. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index b15f159012..7e98a26f38 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -65,7 +65,7 @@ use console::{ types::{Field, Group}, }; use ledger_block::{Deployment, Transition}; -use synthesizer_program::{Closure, Function, Instruction, Operand, Program, traits::*}; +use synthesizer_program::{CallOperator, Closure, Function, Instruction, Operand, Program, traits::*}; use synthesizer_snark::{Certificate, ProvingKey, UniversalSRS, VerifyingKey}; use aleo_std::prelude::{finish, lap, timer}; @@ -189,8 +189,6 @@ pub struct Stack { verifying_keys: Arc, VerifyingKey>>>, /// The mapping of function names to the number of function calls. number_of_calls: IndexMap, usize>, - /// The program depth. - program_depth: usize, /// The program address. program_address: Address, /// The program edition. @@ -246,12 +244,6 @@ impl StackProgram for Stack { self.program.id() } - /// Returns the program depth. - #[inline] - fn program_depth(&self) -> usize { - self.program_depth - } - /// Returns the program address. #[inline] fn program_address(&self) -> &Address { @@ -290,10 +282,25 @@ impl StackProgram for Stack { /// Returns the expected number of calls for the given function name. #[inline] fn get_number_of_calls(&self, function_name: &Identifier) -> Result { - self.number_of_calls - .get(function_name) - .copied() - .ok_or_else(|| anyhow!("Function '{function_name}' does not exist")) + // Initialize the base number of calls. + let mut num_calls = 1; + // Determine the number of calls for the function. + for instruction in self.program.get_function_ref(function_name)?.instructions() { + if let Instruction::Call(call) = instruction { + // Determine if this is a function call. + if call.is_function_call(self)? { + // Increment by the number of calls. + num_calls += match call.operator() { + CallOperator::Locator(locator) => { + self.get_external_stack(locator.program_id())?.get_number_of_calls(locator.resource())? + } + CallOperator::Resource(resource) => self.get_number_of_calls(resource)?, + }; + } + } + } + // Return the number of calls. + Ok(num_calls) } /// Returns a value for the given value type. diff --git a/synthesizer/process/src/verify_execution.rs b/synthesizer/process/src/verify_execution.rs index baff19812f..5470c2127b 100644 --- a/synthesizer/process/src/verify_execution.rs +++ b/synthesizer/process/src/verify_execution.rs @@ -14,6 +14,7 @@ // limitations under the License. use super::*; +use ledger_block::Transaction; impl Process { /// Verifies the given execution is valid. @@ -24,6 +25,11 @@ impl Process { // Ensure the execution contains transitions. ensure!(!execution.is_empty(), "There are no transitions in the execution"); + // Ensure that the execution does not exceed the maximum number of transitions. + ensure!( + execution.len() <= Transaction::::MAX_TRANSITIONS, + "The execution exceeded the maximum number of transitions" + ); // Ensure the number of transitions matches the program function. let locator = { diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index 9b8937c24a..277c2fbaf2 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -65,9 +65,6 @@ pub trait StackProgram { /// Returns the program ID. fn program_id(&self) -> &ProgramID; - /// Returns the program depth. - fn program_depth(&self) -> usize; - /// Returns the program address. fn program_address(&self) -> &Address; From 704098b280df84ecd0ea54906ee68e3ab92012ba Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:12:12 -0800 Subject: [PATCH 18/46] Implement global.get edition ... --- synthesizer/process/src/cost.rs | 2 + .../src/stack/finalize_types/initialize.rs | 37 +++ .../program/src/logic/command/global_get.rs | 233 ++++++++++++++++++ synthesizer/program/src/logic/command/mod.rs | 32 ++- 4 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 synthesizer/program/src/logic/command/global_get.rs diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 40d0a5883a..b08d147042 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -393,6 +393,8 @@ pub fn cost_per_command( Command::GetOrUse(command) => { cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } + // TODO: This should be updated once general `global.get` is implemented. + Command::GlobalGet(_) => Ok(500), Command::RandChaCha(_) => Ok(25_000), Command::Remove(_) => Ok(SET_BASE_COST), Command::Set(command) => { diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index 80e4566a9e..b8375783d4 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -14,6 +14,7 @@ // limitations under the License. use super::*; +use synthesizer_program::GlobalGet; impl FinalizeTypes { /// Initializes a new instance of `FinalizeTypes` for the given finalize. @@ -178,6 +179,7 @@ impl FinalizeTypes { Command::Contains(contains) => self.check_contains(stack, contains)?, Command::Get(get) => self.check_get(stack, get)?, Command::GetOrUse(get_or_use) => self.check_get_or_use(stack, get_or_use)?, + Command::GlobalGet(global_get) => self.check_global_get(stack, global_get)?, Command::RandChaCha(rand_chacha) => self.check_rand_chacha(stack, finalize.name(), rand_chacha)?, Command::Remove(remove) => self.check_remove(stack, finalize.name(), remove)?, Command::Set(set) => self.check_set(stack, finalize.name(), set)?, @@ -460,6 +462,41 @@ impl FinalizeTypes { Ok(()) } + /// Ensures the given `global.get` command is well-formed. + #[inline] + fn check_global_get( + &mut self, + stack: &(impl StackMatches + StackProgram), + global_get: &GlobalGet, + ) -> Result<()> { + // Ensure that the global name is `edition`. + let global_name = match global_get.global() { + CallOperator::Locator(locator) => { + // Retrieve the program ID. + let program_id = locator.program_id(); + // Ensure the locator does not reference the current program. + if stack.program_id() == program_id { + bail!("Locator '{locator}' does not reference an external mapping."); + } + // Ensure the current program contains an import for this external program. + if !stack.program().imports().keys().contains(program_id) { + bail!("External program '{program_id}' is not imported by '{}'.", stack.program_id()); + } + locator.resource() + } + CallOperator::Resource(global_name) => global_name, + }; + ensure!(global_name.to_string() == "edition", "Invalid global name: {global_name}"); + + // Get the destination register. + let destination = global_get.destination().clone(); + // Ensure the destination register is a locator (and does not reference an access). + ensure!(matches!(destination, Register::Locator(..)), "Destination '{destination}' must be a locator."); + // Insert the destination register. + self.add_destination(destination, FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::U16)))?; + Ok(()) + } + /// Ensure the given `rand.chacha` command is well-formed. #[inline] fn check_rand_chacha( diff --git a/synthesizer/program/src/logic/command/global_get.rs b/synthesizer/program/src/logic/command/global_get.rs new file mode 100644 index 0000000000..562fb9aab6 --- /dev/null +++ b/synthesizer/program/src/logic/command/global_get.rs @@ -0,0 +1,233 @@ +// Copyright 2024 Aleo Network Foundation +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + CallOperator, + Opcode, + traits::{FinalizeStoreTrait, RegistersLoad, RegistersStore, StackMatches, StackProgram}, +}; +use console::{ + network::prelude::*, + program::{Literal, Register, Value}, + types::U16, +}; + +/// A global get command, e.g. `global.get owner into r1;`. +/// Gets the value stored at `global` and stores the result in `destination`. +#[derive(Clone)] +pub struct GlobalGet { + /// The global ID. + global: CallOperator, + /// The destination register. + destination: Register, +} + +impl PartialEq for GlobalGet { + /// Returns true if the two objects are equal. + #[inline] + fn eq(&self, other: &Self) -> bool { + self.global == other.global && self.destination == other.destination + } +} + +impl Eq for GlobalGet {} + +impl std::hash::Hash for GlobalGet { + /// Returns the hash of the object. + #[inline] + fn hash(&self, state: &mut H) { + self.global.hash(state); + self.destination.hash(state); + } +} + +impl GlobalGet { + /// Returns the opcode. + #[inline] + pub const fn opcode() -> Opcode { + Opcode::Command("global.get") + } + + /// Returns the global ID. + #[inline] + pub const fn global(&self) -> &CallOperator { + &self.global + } + + /// Returns the destination register. + #[inline] + pub const fn destination(&self) -> &Register { + &self.destination + } +} + +impl GlobalGet { + /// Finalizes the command. + // Note that this implementation is specifically tailored for `global.get edition into r;`. + // This was done to minimize the number of changes introduced to the code base. + // When global values are introduced in the `FinalizeStoreTrait`, this implementation should be refactored. + #[inline] + pub fn finalize( + &self, + stack: &(impl StackMatches + StackProgram), + _store: &impl FinalizeStoreTrait, + registers: &mut (impl RegistersLoad + RegistersStore), + ) -> Result<()> { + // Determine the program ID and global ID. + let (external_stack, global_name) = match self.global { + CallOperator::Locator(locator) => { + (Some(stack.get_external_stack(locator.program_id())?), *locator.resource()) + } + CallOperator::Resource(global_name) => (None, global_name), + }; + + // Ensure that the global name is `edition`. + // This is presently the only valid use of `global.get`. + ensure!(global_name.to_string() == "edition", "Invalid global name: {global_name}"); + + // Lookup the edition in the appropriate stack. + let edition = match external_stack { + Some(external_stack) => external_stack.edition(), + None => stack.edition(), + }; + + // Assign the value to the destination register. + registers.store(stack, &self.destination, Value::from(Literal::U16(U16::new(edition))))?; + + Ok(()) + } +} + +impl Parser for GlobalGet { + /// Parses a string into the command. + #[inline] + fn parse(string: &str) -> ParserResult { + // Parse the whitespace and comments from the string. + let (string, _) = Sanitizer::parse(string)?; + // Parse the opcode from the string. + let (string, _) = tag(*Self::opcode())(string)?; + // Parse the whitespace from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + + // Parse the global name from the string. + let (string, global) = CallOperator::parse(string)?; + + // Parse the whitespace from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + // Parse the "into" keyword from the string. + let (string, _) = tag("into")(string)?; + // Parse the whitespace from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + // Parse the destination register from the string. + let (string, destination) = Register::parse(string)?; + + // Parse the whitespace from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + // Parse the ";" from the string. + let (string, _) = tag(";")(string)?; + + Ok((string, Self { global, destination })) + } +} + +impl FromStr for GlobalGet { + type Err = Error; + + /// Parses a string into the command. + #[inline] + fn from_str(string: &str) -> Result { + match Self::parse(string) { + Ok((remainder, object)) => { + // Ensure the remainder is empty. + ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\""); + // Return the object. + Ok(object) + } + Err(error) => bail!("Failed to parse string. {error}"), + } + } +} + +impl Debug for GlobalGet { + /// Prints the command as a string. + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for GlobalGet { + /// Prints the command to a string. + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // Print the command. + write!(f, "{} ", Self::opcode())?; + // Print the global ID. + write!(f, "{} into ", self.global)?; + // Print the destination register. + write!(f, "{};", self.destination) + } +} + +impl FromBytes for GlobalGet { + /// Reads the command from a buffer. + fn read_le(mut reader: R) -> IoResult { + // Read the global ID. + let global = CallOperator::read_le(&mut reader)?; + // Read the destination register. + let destination = Register::read_le(&mut reader)?; + // Return the command. + Ok(Self { global, destination }) + } +} + +impl ToBytes for GlobalGet { + /// Writes the command to a buffer. + fn write_le(&self, mut writer: W) -> IoResult<()> { + // Write the global ID. + self.global.write_le(&mut writer)?; + // Write the destination register. + self.destination.write_le(&mut writer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use console::{network::MainnetV0, program::Register}; + + type CurrentNetwork = MainnetV0; + + #[test] + fn test_parse() { + let (string, global_get) = GlobalGet::::parse("global.get edition into r1;").unwrap(); + assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); + assert_eq!(global_get.global(), &CallOperator::from_str("edition").unwrap()); + assert_eq!(global_get.destination, Register::Locator(1), "The destination is incorrect"); + + let (string, global_get) = + GlobalGet::::parse("global.get token.aleo/edition into r1;").unwrap(); + assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); + assert_eq!(global_get.global(), &CallOperator::from_str("token.aleo/edition").unwrap()); + assert_eq!(global_get.destination, Register::Locator(1), "The destination is incorrect"); + } + + #[test] + fn test_from_bytes() { + let (string, get) = GlobalGet::::parse("global.get edition into r1;").unwrap(); + assert!(string.is_empty()); + let bytes_le = get.to_bytes_le().unwrap(); + let result = GlobalGet::::from_bytes_le(&bytes_le[..]); + assert!(result.is_ok()) + } +} diff --git a/synthesizer/program/src/logic/command/mod.rs b/synthesizer/program/src/logic/command/mod.rs index e31408db76..381a151b40 100644 --- a/synthesizer/program/src/logic/command/mod.rs +++ b/synthesizer/program/src/logic/command/mod.rs @@ -28,6 +28,9 @@ pub use get::*; mod get_or_use; pub use get_or_use::*; +mod global_get; +pub use global_get::*; + mod rand_chacha; pub use crate::command::rand_chacha::*; @@ -73,6 +76,8 @@ pub enum Command { /// Gets the value stored at the `key` operand in `mapping` and stores the result into `destination`. /// If the key is not present, `default` is stored `destination`. GetOrUse(GetOrUse), + /// Gets the value stored at `global` and stores the result into `destination`. + GlobalGet(GlobalGet), /// Generates a random value using the `rand.chacha` command and stores the result into `destination`. RandChaCha(RandChaCha), /// Removes the (`key`, `value`) entry from the `mapping`. @@ -96,6 +101,7 @@ impl CommandTrait for Command { Command::Contains(contains) => vec![contains.destination().clone()], Command::Get(get) => vec![get.destination().clone()], Command::GetOrUse(get_or_use) => vec![get_or_use.destination().clone()], + Command::GlobalGet(global_get) => vec![global_get.destination().clone()], Command::RandChaCha(rand_chacha) => vec![rand_chacha.destination().clone()], Command::Await(_) | Command::BranchEq(_) @@ -165,6 +171,8 @@ impl Command { Command::Get(get) => get.finalize(stack, store, registers).map(|_| None), // Finalize the 'get.or_use' command, and return no finalize operation. Command::GetOrUse(get_or_use) => get_or_use.finalize(stack, store, registers).map(|_| None), + // Finalize the 'global.get' command, and return no finalize operation. + Command::GlobalGet(global_get) => global_get.finalize(stack, store, registers).map(|_| None), // Finalize the `rand.chacha` command, and return no finalize operation. Command::RandChaCha(rand_chacha) => rand_chacha.finalize(stack, registers).map(|_| None), // Finalize the 'remove' command, and return the finalize operation. @@ -209,8 +217,10 @@ impl FromBytes for Command { 9 => Ok(Self::BranchNeq(BranchNeq::read_le(&mut reader)?)), // Read the `position` command. 10 => Ok(Self::Position(Position::read_le(&mut reader)?)), + // Read the `global.get` command. + 11 => Ok(Self::GlobalGet(GlobalGet::read_le(&mut reader)?)), // Invalid variant. - 11.. => Err(error(format!("Invalid command variant: {variant}"))), + 12.. => Err(error(format!("Invalid command variant: {variant}"))), } } } @@ -285,6 +295,12 @@ impl ToBytes for Command { // Write the position command. position.write_le(&mut writer) } + Self::GlobalGet(global_get) => { + // Write the variant. + 11u8.write_le(&mut writer)?; + // Write the `global.get` command. + global_get.write_le(&mut writer) + } } } } @@ -300,6 +316,7 @@ impl Parser for Command { map(Contains::parse, |contains| Self::Contains(contains)), map(GetOrUse::parse, |get_or_use| Self::GetOrUse(get_or_use)), map(Get::parse, |get| Self::Get(get)), + map(GlobalGet::parse, |global_get| Self::GlobalGet(global_get)), map(RandChaCha::parse, |rand_chacha| Self::RandChaCha(rand_chacha)), map(Remove::parse, |remove| Self::Remove(remove)), map(Set::parse, |set| Self::Set(set)), @@ -345,6 +362,7 @@ impl Display for Command { Self::Contains(contains) => Display::fmt(contains, f), Self::Get(get) => Display::fmt(get, f), Self::GetOrUse(get_or_use) => Display::fmt(get_or_use, f), + Self::GlobalGet(global_get) => Display::fmt(global_get, f), Self::RandChaCha(rand_chacha) => Display::fmt(rand_chacha, f), Self::Remove(remove) => Display::fmt(remove, f), Self::Set(set) => Display::fmt(set, f), @@ -402,6 +420,12 @@ mod tests { let bytes = command.to_bytes_le().unwrap(); assert_eq!(command, Command::from_bytes_le(&bytes).unwrap()); + // GlobalGet + let expected = "global.get edition into r0;"; + let command = Command::::parse(expected).unwrap().1; + let bytes = command.to_bytes_le().unwrap(); + assert_eq!(command, Command::from_bytes_le(&bytes).unwrap()); + // RandChaCha let expected = "rand.chacha into r1 as field;"; let command = Command::::parse(expected).unwrap().1; @@ -479,6 +503,12 @@ mod tests { assert_eq!(Command::GetOrUse(GetOrUse::from_str(expected).unwrap()), command); assert_eq!(expected, command.to_string()); + // GlobalGet + let expected = "global.get edition into r0;"; + let command = Command::::parse(expected).unwrap().1; + assert_eq!(Command::GlobalGet(GlobalGet::from_str(expected).unwrap()), command); + assert_eq!(expected, command.to_string()); + // RandChaCha let expected = "rand.chacha into r1 as field;"; let command = Command::::parse(expected).unwrap().1; From 9828994c4f5cf677c9d5aea72ce8d4bf36249b46 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:39:28 -0800 Subject: [PATCH 19/46] Add test --- .../global_get_editions.aleo | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo new file mode 100644 index 0000000000..d188ce3668 --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo @@ -0,0 +1,53 @@ +/* +randomness: 89247928 +cases: + - program: first.aleo + function: a + inputs: [0u16] + - program: first.aleo + function: a + inputs: [1u16] + - program: second.aleo + function: a + inputs: [0u16] + - program: second.aleo + function: a + inputs: [1u16] +*/ + +program first.aleo; + +function a: + input r0 as u16.public; + async a r0 into r1; + output r1 as first.aleo/a.future; +finalize a: + input r0 as u16.public; + global.get edition into r1; + assert.eq r0 r1; + +///////////////////////////////////////////////// + +program second.aleo; + +function a: + input r0 as u16.public; + async a r0 into r1; + output r1 as second.aleo/a.future; +finalize a: + input r0 as u16.public; + global.get edition into r1; + assert.eq r0 r1; + +///////////////////////////////////////////////// + +program second.aleo; + +function a: + input r0 as u16.public; + async a r0 into r1; + output r1 as second.aleo/a.future; +finalize a: + input r0 as u16.public; + global.get edition into r1; + assert.eq r0 r1; From 965e9e16fb1751dd05e86e277ad43ffd896f0d9e Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:39:40 -0800 Subject: [PATCH 20/46] Place stacks under a RwLock for interior mutability --- synthesizer/process/src/cost.rs | 4 +-- synthesizer/process/src/finalize.rs | 8 ++--- synthesizer/process/src/lib.rs | 36 +++++++++---------- .../process/src/stack/helpers/check_update.rs | 3 +- .../process/src/stack/helpers/initialize.rs | 2 +- synthesizer/process/src/stack/mod.rs | 14 ++++---- synthesizer/process/src/tests/test_credits.rs | 10 +++--- synthesizer/process/src/tests/test_execute.rs | 2 +- synthesizer/process/src/verify_execution.rs | 3 +- synthesizer/process/src/verify_fee.rs | 10 +++--- synthesizer/src/vm/mod.rs | 3 ++ vm/package/build.rs | 19 ++++++---- vm/package/execute.rs | 19 +++++++--- 13 files changed, 78 insertions(+), 55 deletions(-) diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index b08d147042..0e500a2ce5 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -68,7 +68,7 @@ pub fn execution_cost_v2(process: &Process, execution: &Execution // Get the finalize cost for the root transition. let stack = process.get_stack(transition.program_id())?; - let finalize_cost = cost_in_microcredits_v2(stack, transition.function_name())?; + let finalize_cost = cost_in_microcredits_v2(&stack, transition.function_name())?; // Compute the total cost in microcredits. let total_cost = storage_cost @@ -88,7 +88,7 @@ pub fn execution_cost_v1(process: &Process, execution: &Execution // Get the finalize cost for the root transition. let stack = process.get_stack(transition.program_id())?; - let finalize_cost = cost_in_microcredits_v1(stack, transition.function_name())?; + let finalize_cost = cost_in_microcredits_v1(&stack, transition.function_name())?; // Compute the total cost in microcredits. let total_cost = storage_cost diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 48699f36d5..f1519682f8 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -80,7 +80,7 @@ impl Process { // Retrieve the fee stack. let fee_stack = self.get_stack(fee.program_id())?; // Finalize the fee transition. - finalize_operations.extend(finalize_fee_transition(state, store, fee_stack, fee)?); + finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?); lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name()); /* Finalize the deployment. */ @@ -140,7 +140,7 @@ impl Process { // Finalize the root transition. // Note that this will result in all the remaining transitions being finalized, since the number // of calls matches the number of transitions. - let mut finalize_operations = finalize_transition(state, store, stack, transition, call_graph)?; + let mut finalize_operations = finalize_transition(state, store, &stack, transition, call_graph)?; /* Finalize the fee. */ @@ -148,7 +148,7 @@ impl Process { // Retrieve the fee stack. let fee_stack = self.get_stack(fee.program_id())?; // Finalize the fee transition. - finalize_operations.extend(finalize_fee_transition(state, store, fee_stack, fee)?); + finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?); lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name()); } @@ -174,7 +174,7 @@ impl Process { // Retrieve the stack. let stack = self.get_stack(fee.program_id())?; // Finalize the fee transition. - let result = finalize_fee_transition(state, store, stack, fee); + let result = finalize_fee_transition(state, store, &stack, fee); finish!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name()); // Return the result. result diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index 473ca9f57e..bd795d68b4 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -79,7 +79,7 @@ pub struct Process { /// The universal SRS. universal_srs: Arc>, /// The mapping of program IDs to stacks. - stacks: Arc, Arc>>>, + stacks: Arc, Arc>>>>, } impl Process { @@ -89,7 +89,8 @@ impl Process { let timer = timer!("Process:setup"); // Initialize the process. - let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(IndexMap::new()) }; + let mut process = + Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(RwLock::new(IndexMap::new())) }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -137,11 +138,10 @@ impl Process { pub fn add_stack(&mut self, stack: Stack) -> Result<()> { // Get the program ID. let program_id = *stack.program_id(); - // Add the stack to the process. - Arc::get_mut(&mut self.stacks) - .ok_or_else(|| anyhow!("Failed to add stack"))? - .insert(program_id, Arc::new(stack)); - + // Acquire the write lock. + let mut stacks = self.stacks.write(); + // Insert the stack into the process, replacing the existing stack if it exists. + stacks.insert(program_id, Arc::new(stack)); Ok(()) } } @@ -153,7 +153,8 @@ impl Process { let timer = timer!("Process::load"); // Initialize the process. - let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(IndexMap::new()) }; + let mut process = + Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(RwLock::new(IndexMap::new())) }; lap!(timer, "Initialize process"); // Initialize the 'credits.aleo' program. @@ -194,7 +195,8 @@ impl Process { #[cfg(feature = "wasm")] pub fn load_web() -> Result { // Initialize the process. - let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(IndexMap::new()) }; + let mut process = + Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: Arc::new(RwLock::new(IndexMap::new())) }; // Initialize the 'credits.aleo' program. let program = Program::credits()?; @@ -221,26 +223,22 @@ impl Process { /// Returns `true` if the process contains the program with the given ID. #[inline] pub fn contains_program(&self, program_id: &ProgramID) -> bool { - self.stacks.contains_key(program_id) + self.stacks.read().contains_key(program_id) } /// Returns the stack for the given program ID. #[inline] - pub fn get_stack(&self, program_id: impl TryInto>) -> Result<&Arc>> { + pub fn get_stack(&self, program_id: impl TryInto>) -> Result>> { // Prepare the program ID. let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?; + // Acquire the read lock. + let stacks = self.stacks.read(); // Retrieve the stack. - let stack = self.stacks.get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; + let stack = stacks.get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?; // Ensure the program ID matches. ensure!(stack.program_id() == &program_id, "Expected program '{}', found '{program_id}'", stack.program_id()); // Return the stack. - Ok(stack) - } - - /// Returns the program for the given program ID. - #[inline] - pub fn get_program(&self, program_id: impl TryInto>) -> Result<&Program> { - Ok(self.get_stack(program_id)?.program()) + Ok(stack.clone()) } /// Returns the proving key for the given program ID and function name. diff --git a/synthesizer/process/src/stack/helpers/check_update.rs b/synthesizer/process/src/stack/helpers/check_update.rs index 28bd548c48..b18255efdb 100644 --- a/synthesizer/process/src/stack/helpers/check_update.rs +++ b/synthesizer/process/src/stack/helpers/check_update.rs @@ -22,7 +22,8 @@ impl Stack { // Get the new program ID. let program_id = program.id(); // Get the old program. - let old_program = process.get_stack(program.id())?.program(); + let stack = process.get_stack(program_id)?; + let old_program = stack.program(); // Ensure the program ID matches. ensure!(old_program.id() == program.id(), "Cannot update '{program_id}' with different program ID"); diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index d04920c071..bce4dab05c 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -25,7 +25,7 @@ impl Stack { // Construct the stack for the program. let mut stack = Self { program: program.clone(), - stacks: Arc::downgrade(&process.stacks), + stacks: process.stacks.clone(), register_types: Default::default(), finalize_types: Default::default(), universal_srs: process.universal_srs().clone(), diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 7e98a26f38..6424f4bc04 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -71,7 +71,7 @@ use synthesizer_snark::{Certificate, ProvingKey, UniversalSRS, VerifyingKey}; use aleo_std::prelude::{finish, lap, timer}; use indexmap::IndexMap; use parking_lot::RwLock; -use std::sync::{Arc, Weak}; +use std::sync::Arc; #[cfg(not(feature = "serial"))] use rayon::prelude::*; @@ -175,8 +175,8 @@ impl CallStack { pub struct Stack { /// The program (record types, structs, functions). program: Program, - /// A weak reference to the global stack map. - stacks: Weak, Arc>>>, + /// A reference to the global stack map. + stacks: Arc, Arc>>>>, /// The mapping of closure and function names to their register types. register_types: IndexMap, RegisterTypes>, /// The mapping of finalize names to their register types. @@ -261,10 +261,12 @@ impl StackProgram for Stack { fn get_external_stack(&self, program_id: &ProgramID) -> Result>> { // Check that the program ID is imported by the program. ensure!(self.program.contains_import(program_id), "External program '{program_id}' is not imported."); - // Upgrade the reference to the global stack map. - let stacks = self.stacks.upgrade().ok_or_else(|| anyhow!("Global stack map does not exist."))?; // Retrieve the stack. - stacks.get(program_id).cloned().ok_or_else(|| anyhow!("External program '{program_id}' does not exist.")) + self.stacks + .read() + .get(program_id) + .cloned() + .ok_or_else(|| anyhow!("External program '{program_id}' does not exist.")) } /// Returns the function with the given function name. diff --git a/synthesizer/process/src/tests/test_credits.rs b/synthesizer/process/src/tests/test_credits.rs index 9de634854f..b3b423b327 100644 --- a/synthesizer/process/src/tests/test_credits.rs +++ b/synthesizer/process/src/tests/test_credits.rs @@ -2890,7 +2890,7 @@ mod sanity_checks { let r2 = Value::::from_str("1_500_000_000_000_000_u64").unwrap(); // Compute the assignment. - let assignment = get_assignment::<_, CurrentAleo>(stack, &private_key, function_name, &[r0, r1, r2], rng); + let assignment = get_assignment::<_, CurrentAleo>(&stack, &private_key, function_name, &[r0, r1, r2], rng); assert_eq!(16, assignment.num_public()); assert_eq!(50956, assignment.num_private()); assert_eq!(51002, assignment.num_constraints()); @@ -2918,7 +2918,7 @@ mod sanity_checks { let r1 = Value::::from_str("1_500_000_000_000_000_u64").unwrap(); // Compute the assignment. - let assignment = get_assignment::<_, CurrentAleo>(stack, &private_key, function_name, &[r0, r1], rng); + let assignment = get_assignment::<_, CurrentAleo>(&stack, &private_key, function_name, &[r0, r1], rng); assert_eq!(11, assignment.num_public()); assert_eq!(12318, assignment.num_private()); assert_eq!(12325, assignment.num_constraints()); @@ -2946,7 +2946,7 @@ mod sanity_checks { let r1 = Value::::from_str("1_500_000_000_000_000_u64").unwrap(); // Compute the assignment. - let assignment = get_assignment::<_, CurrentAleo>(stack, &private_key, function_name, &[r0, r1], rng); + let assignment = get_assignment::<_, CurrentAleo>(&stack, &private_key, function_name, &[r0, r1], rng); assert_eq!(11, assignment.num_public()); assert_eq!(12323, assignment.num_private()); assert_eq!(12330, assignment.num_constraints()); @@ -2980,7 +2980,7 @@ mod sanity_checks { let r3 = Value::::from_str(&Field::::rand(rng).to_string()).unwrap(); // Compute the assignment. - let assignment = get_assignment::<_, CurrentAleo>(stack, &private_key, function_name, &[r0, r1, r2, r3], rng); + let assignment = get_assignment::<_, CurrentAleo>(&stack, &private_key, function_name, &[r0, r1, r2, r3], rng); assert_eq!(15, assignment.num_public()); assert_eq!(38115, assignment.num_private()); assert_eq!(38151, assignment.num_constraints()); @@ -3008,7 +3008,7 @@ mod sanity_checks { let r2 = Value::::from_str(&Field::::rand(rng).to_string()).unwrap(); // Compute the assignment. - let assignment = get_assignment::<_, CurrentAleo>(stack, &private_key, function_name, &[r0, r1, r2], rng); + let assignment = get_assignment::<_, CurrentAleo>(&stack, &private_key, function_name, &[r0, r1, r2], rng); assert_eq!(12, assignment.num_public()); assert_eq!(12920, assignment.num_private()); assert_eq!(12930, assignment.num_constraints()); diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index a2311f57ca..bac91dcbf5 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -2366,7 +2366,7 @@ fn test_process_deploy_credits_program() { // Initialize an empty process without the `credits` program. let empty_process = Process { universal_srs: Arc::new(UniversalSRS::::load().unwrap()), - stacks: Arc::new(IndexMap::new()), + stacks: Arc::new(RwLock::new(IndexMap::new())), }; // Construct the process. diff --git a/synthesizer/process/src/verify_execution.rs b/synthesizer/process/src/verify_execution.rs index 5470c2127b..e7bbea558c 100644 --- a/synthesizer/process/src/verify_execution.rs +++ b/synthesizer/process/src/verify_execution.rs @@ -195,7 +195,8 @@ impl Process { }; // Retrieve the adress belonging to the parent. - let parent_address = self.get_stack(parent)?.program_address(); + let stack = self.get_stack(parent)?; + let parent_address = stack.program_address(); // Compute the x- and y-coordinate of `parent`. let (parent_x, parent_y) = parent_address.to_xy_coordinates(); diff --git a/synthesizer/process/src/verify_fee.rs b/synthesizer/process/src/verify_fee.rs index 02d44de3e5..2b669748b3 100644 --- a/synthesizer/process/src/verify_fee.rs +++ b/synthesizer/process/src/verify_fee.rs @@ -126,7 +126,8 @@ impl Process { let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates(); // Retrieve the adress belonging to the program ID. - let program_adress = self.get_stack(fee.program_id())?.program_address(); + let stack = self.get_stack(fee.program_id())?; + let program_adress = stack.program_address(); // Compute the x- and y-coordinate of `parent`. let (parent_x, parent_y) = program_adress.to_xy_coordinates(); @@ -145,7 +146,7 @@ impl Process { println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs); // Retrieve the verifying key. - let verifying_key = self.get_verifying_key(fee.program_id(), fee.function_name())?; + let verifying_key = stack.get_verifying_key(fee.function_name())?; // Ensure the fee proof is valid. Trace::verify_fee_proof((verifying_key, vec![inputs]), fee)?; @@ -198,7 +199,8 @@ impl Process { let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates(); // Retrieve the adress belonging to the program ID. - let program_adress = self.get_stack(fee.program_id())?.program_address(); + let stack = self.get_stack(fee.program_id())?; + let program_adress = stack.program_address(); // Compute the x- and y-coordinate of `parent`. let (parent_x, parent_y) = program_adress.to_xy_coordinates(); @@ -217,7 +219,7 @@ impl Process { println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs); // Retrieve the verifying key. - let verifying_key = self.get_verifying_key(fee.program_id(), fee.function_name())?; + let verifying_key = stack.get_verifying_key(fee.function_name())?; // Ensure the fee proof is valid. Trace::verify_fee_proof((verifying_key, vec![inputs]), fee)?; diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index aed27fb2bc..5da9cf609a 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -97,7 +97,9 @@ impl> VM { #[inline] pub fn from(store: ConsensusStore) -> Result { // Initialize a new process. + println!("Initializing new process"); let mut process = Process::load()?; + println!("Finished initializing new process"); // Initialize the store for 'credits.aleo'. let credits = Program::::credits()?; @@ -180,6 +182,7 @@ impl> VM { for (program_id, deployment) in deployments.iter().flatten() { // Load the deployment if it does not exist in the process yet. + println!("Loading: {program_id}"); if !process.contains_program(program_id) { process.load_deployment(deployment)?; } diff --git a/vm/package/build.rs b/vm/package/build.rs index 251e8cf1b5..0a90387cfe 100644 --- a/vm/package/build.rs +++ b/vm/package/build.rs @@ -14,6 +14,7 @@ // limitations under the License. use super::*; +use snarkvm_synthesizer::program::StackProgram; use snarkvm_utilities::DeserializeExt; @@ -181,7 +182,7 @@ impl Package { let imported_programs = program .imports() .keys() - .map(|program_id| process.get_program(program_id).cloned()) + .map(|program_id| process.get_stack(program_id).map(|stack| stack.program().clone())) .collect::>>()?; // Synthesize each proving and verifying key. @@ -220,18 +221,24 @@ impl Package { // Load each function circuit. for function_name in program.functions().keys() { // Retrieve the program. - let program = process.get_program(program_id)?; + let stack = process.get_stack(program_id)?; + let program = stack.program(); // Retrieve the function from the program. let function = program.get_function(function_name)?; // Save all the prover and verifier files for any function calls that are made. for instruction in function.instructions() { if let Instruction::Call(call) = instruction { - // Retrieve the program and resource. - let (program, resource) = match call.operator() { + // Get the external stack and resource. + let (external_stack, resource) = match call.operator() { CallOperator::Locator(locator) => { - (process.get_program(locator.program_id())?, locator.resource()) + (Some(process.get_stack(locator.program_id())?), locator.resource()) } - CallOperator::Resource(resource) => (program, resource), + CallOperator::Resource(resource) => (None, resource), + }; + // Retrieve the program. + let program = match &external_stack { + Some(external_stack) => external_stack.program(), + None => program, }; // If this is a function call, save its corresponding prover and verifier files. if program.contains_function(resource) { diff --git a/vm/package/execute.rs b/vm/package/execute.rs index e848a14ad9..84946323dd 100644 --- a/vm/package/execute.rs +++ b/vm/package/execute.rs @@ -14,6 +14,7 @@ // limitations under the License. use super::*; +use snarkvm_synthesizer::program::StackProgram; impl Package { /// Executes a program function with the given inputs. @@ -52,16 +53,24 @@ impl Package { let authorization = process.authorize::(private_key, program_id, function_name, inputs.iter(), rng)?; // Retrieve the program. - let program = process.get_program(program_id)?; + let stack = process.get_stack(program_id)?; + let program = stack.program(); // Retrieve the function from the program. let function = program.get_function(&function_name)?; // Save all the prover and verifier files for any function calls that are made. for instruction in function.instructions() { if let Instruction::Call(call) = instruction { - // Retrieve the program and resource. - let (program, resource) = match call.operator() { - CallOperator::Locator(locator) => (process.get_program(locator.program_id())?, locator.resource()), - CallOperator::Resource(resource) => (program, resource), + // Retrieve the external stack and resource. + let (external_stack, resource) = match call.operator() { + CallOperator::Locator(locator) => { + (Some(process.get_stack(locator.program_id())?), locator.resource()) + } + CallOperator::Resource(resource) => (None, resource), + }; + // Retrieve the program. + let program = match &external_stack { + Some(external_stack) => external_stack.program(), + None => program, }; // If this is a function call, save its corresponding prover and verifier files. if program.contains_function(resource) { From 97cee4806393956aa9a770f74673aee4aba83365 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:17:30 -0800 Subject: [PATCH 21/46] global.get test case passes --- .../global_get_editions.out | 47 +++++++++++++++++++ .../global_get_editions.aleo | 13 ----- 2 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 synthesizer/tests/expectations/vm/execute_and_finalize/global_get_editions.out diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/global_get_editions.out b/synthesizer/tests/expectations/vm/execute_and_finalize/global_get_editions.out new file mode 100644 index 0000000000..dfe41c3cac --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/global_get_editions.out @@ -0,0 +1,47 @@ +errors: [] +outputs: +- verified: true + execute: + first.aleo/a: + outputs: + - '{"type":"future","id":"2809153169238796095987200430148602762586641507642945792553422332319686895519field","value":"{\n program_id: first.aleo,\n function_name: a,\n arguments: [\n 0u16\n ]\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +- verified: true + execute: + first.aleo/a: + outputs: + - '{"type":"future","id":"6624578975206627096391457370176077158356446724995653059907572840259328387288field","value":"{\n program_id: first.aleo,\n function_name: a,\n arguments: [\n 1u16\n ]\n}"}' + speculate: the execution was rejected + add_next_block: succeeded. +- verified: true + execute: + second.aleo/a: + outputs: + - '{"type":"future","id":"4159938038415297757000623408673685308057279543422402726232827422415160043417field","value":"{\n program_id: second.aleo,\n function_name: a,\n arguments: [\n 0u16\n ]\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +- verified: true + execute: + second.aleo/a: + outputs: + - '{"type":"future","id":"1867225070172867121370839445911564700232643554594642294523006576869351422303field","value":"{\n program_id: second.aleo,\n function_name: a,\n arguments: [\n 1u16\n ]\n}"}' + speculate: the execution was rejected + add_next_block: succeeded. +additional: +- child_outputs: + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"6335672105051103267728583141852688080485262299253217030236120177342400050635field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo124d5ltmgeqn8kv6t7hkkl3jkd87ntxgq5xsepn5s0wmae83zuv9spt9xt3,\n 2231u64\n ]\n}"}' +- child_outputs: + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"5476445033847540619714606143538069318103242529894793064885248999984257781616field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo124d5ltmgeqn8kv6t7hkkl3jkd87ntxgq5xsepn5s0wmae83zuv9spt9xt3,\n 2231u64\n ]\n}"}' +- child_outputs: + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"6985081861400418530732383211199441472158966476330788945580477932446147002838field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo124d5ltmgeqn8kv6t7hkkl3jkd87ntxgq5xsepn5s0wmae83zuv9spt9xt3,\n 2233u64\n ]\n}"}' +- child_outputs: + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"2628250398570108525063369984297078971281746468860790288043327834317563031306field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo124d5ltmgeqn8kv6t7hkkl3jkd87ntxgq5xsepn5s0wmae83zuv9spt9xt3,\n 2233u64\n ]\n}"}' diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo index d188ce3668..ed9229bcce 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo @@ -38,16 +38,3 @@ finalize a: input r0 as u16.public; global.get edition into r1; assert.eq r0 r1; - -///////////////////////////////////////////////// - -program second.aleo; - -function a: - input r0 as u16.public; - async a r0 into r1; - output r1 as second.aleo/a.future; -finalize a: - input r0 as u16.public; - global.get edition into r1; - assert.eq r0 r1; From 6d9060e254e00ba6428fc7a35ad4afb6a89a1fce Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:49:28 -0800 Subject: [PATCH 22/46] Verify edition is only valid global --- ledger/src/tests.rs | 2 ++ synthesizer/process/src/tests/test_update.rs | 0 .../{global_get_editions.out => global_get.out} | 0 .../execute_and_finalize/invalid_global_fail.out | 3 +++ ...{global_get_editions.aleo => global_get.aleo} | 0 .../invalid_global_fail.aleo | 16 ++++++++++++++++ 6 files changed, 21 insertions(+) create mode 100644 synthesizer/process/src/tests/test_update.rs rename synthesizer/tests/expectations/vm/execute_and_finalize/{global_get_editions.out => global_get.out} (100%) create mode 100644 synthesizer/tests/expectations/vm/execute_and_finalize/invalid_global_fail.out rename synthesizer/tests/tests/vm/execute_and_finalize/{global_get_editions.aleo => global_get.aleo} (100%) create mode 100644 synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index d3aacbd33c..98eaebfb95 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -2576,6 +2576,8 @@ function foo: } } + + // These tests require the proof targets to be low enough to be able to generate **valid** solutions. // This requires the 'test' feature to be enabled for the `console` dependency. #[cfg(feature = "test")] diff --git a/synthesizer/process/src/tests/test_update.rs b/synthesizer/process/src/tests/test_update.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/global_get_editions.out b/synthesizer/tests/expectations/vm/execute_and_finalize/global_get.out similarity index 100% rename from synthesizer/tests/expectations/vm/execute_and_finalize/global_get_editions.out rename to synthesizer/tests/expectations/vm/execute_and_finalize/global_get.out diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/invalid_global_fail.out b/synthesizer/tests/expectations/vm/execute_and_finalize/invalid_global_fail.out new file mode 100644 index 0000000000..7a3c66862b --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/invalid_global_fail.out @@ -0,0 +1,3 @@ +errors: +- 'Failed to run `VM::deploy for program first.aleo: Invalid global name: foo' +outputs: [] diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/global_get.aleo similarity index 100% rename from synthesizer/tests/tests/vm/execute_and_finalize/global_get_editions.aleo rename to synthesizer/tests/tests/vm/execute_and_finalize/global_get.aleo diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo new file mode 100644 index 0000000000..f7e02ac4d8 --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo @@ -0,0 +1,16 @@ +/* +randomness: 89247928 +cases: [] +*/ + +program first.aleo; + +function a: + input r0 as u16.public; + async a r0 into r1; + output r1 as first.aleo/a.future; +finalize a: + input r0 as u16.public; + global.get foo into r1; + assert.eq r0 r1; + From c0f489b7a856d4180337be62e6559dee2e268b4e Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:02:02 -0800 Subject: [PATCH 23/46] Test template for (in)validity checks --- synthesizer/process/src/finalize.rs | 1 + synthesizer/process/src/tests/mod.rs | 1 + synthesizer/process/src/tests/test_update.rs | 133 +++++++++++++++++++ synthesizer/program/src/lib.rs | 12 +- 4 files changed, 141 insertions(+), 6 deletions(-) diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index f1519682f8..c077833542 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -101,6 +101,7 @@ impl Process { /// Finalizes the execution and fee. /// This method assumes the given execution **is valid**. + /// TODO: We should specify what valid means here. /// This method should **only** be called by `VM::finalize()`. #[inline] pub fn finalize_execution>( diff --git a/synthesizer/process/src/tests/mod.rs b/synthesizer/process/src/tests/mod.rs index 342a746f9b..b9fd5358df 100644 --- a/synthesizer/process/src/tests/mod.rs +++ b/synthesizer/process/src/tests/mod.rs @@ -15,3 +15,4 @@ pub mod test_credits; pub mod test_execute; +pub mod test_update; diff --git a/synthesizer/process/src/tests/test_update.rs b/synthesizer/process/src/tests/test_update.rs index e69de29bb2..3172a11b01 100644 --- a/synthesizer/process/src/tests/test_update.rs +++ b/synthesizer/process/src/tests/test_update.rs @@ -0,0 +1,133 @@ +// Copyright 2024 Aleo Network Foundation +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + CallStack, + Process, + Stack, + Trace, + traits::{StackEvaluate, StackExecute}, +}; +use circuit::{Aleo, network::AleoV0}; +use console::{ + account::{Address, PrivateKey, ViewKey}, + network::{MainnetV0, prelude::*}, + program::{Identifier, Literal, Plaintext, ProgramID, Record, Value}, + types::{Field, U64}, +}; +use ledger_block::{Fee, Transaction}; +use ledger_query::Query; +use ledger_store::{ + BlockStorage, + BlockStore, + FinalizeStorage, + FinalizeStore, + helpers::memory::{BlockMemory, FinalizeMemory}, +}; +use synthesizer_program::{FinalizeGlobalState, FinalizeStoreTrait, Import, Program, StackProgram}; +use synthesizer_snark::UniversalSRS; + +use indexmap::IndexMap; +use parking_lot::RwLock; +use std::sync::Arc; + +type CurrentNetwork = MainnetV0; +type CurrentAleo = AleoV0; + +/// Samples the default program to test updates on. +fn default_program() -> Program { + Program::from_str( + " + import credits.aleo; + + program basic.aleo; + + struct bundle: + first as u8; + second as u8; + + record data: + owner as address.private; + data as bundle.private; + + mapping onchain: + key as u8.public; + value as u8.public; + + closure sum: + input r0 as u8; + input r1 as u8; + add r0 r1 into r2; + output r2 as u8; + + function adder: + input r0 as u8.private; + input r1 as u8.private; + call sum r0 r1 into r2; + output r2 as u8.private; + + function create_data: + input r0 as u8.private; + input r1 as u8.private; + cast r0 r1 into r2 as bundle; + cast self.caller r2 into r3 as data.record; + output r3 as data.record; + + function store_data: + input r0 as u8.public; + input r1 as u8.public; + async store_data r0 r1 into r2; + output r2 as basic.aleo/store_data.future; + + finalize store_data: + input r0 as u8.public; + input r1 as u8.public; + set r1 into onchain[r0]; + ", + ) + .unwrap() +} + +/// Samples a `Process` with a default program to test updates on. +fn sample_process() -> Result> { + // Sample the process. + let mut process = Process::load()?; + // Add the default program to the process. + process.add_program(&default_program())?; + // Check that the edition of program is 0. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 0); + Ok(process) +} + +#[test] +fn test_update_with_additional_import() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add a dummy program to the process. + let dummy_program = Program::from_str("program dummy.aleo;function foo:")?; + process.add_program(&dummy_program)?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new import. + new_program.add_import(Import::from_str("import dummy.aleo;")?)?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().imports().len(), 2); + Ok(()) +} diff --git a/synthesizer/program/src/lib.rs b/synthesizer/program/src/lib.rs index b9cad0e348..6dec95cb1f 100644 --- a/synthesizer/program/src/lib.rs +++ b/synthesizer/program/src/lib.rs @@ -294,7 +294,7 @@ impl, Command: CommandTrait> Pro /// # Errors /// This method will halt if the imported program was previously added. #[inline] - fn add_import(&mut self, import: Import) -> Result<()> { + pub fn add_import(&mut self, import: Import) -> Result<()> { // Retrieve the imported program name. let import_name = *import.name(); @@ -328,7 +328,7 @@ impl, Command: CommandTrait> Pro /// This method will halt if the mapping name is already in use. /// This method will halt if the mapping name is a reserved opcode or keyword. #[inline] - fn add_mapping(&mut self, mapping: Mapping) -> Result<()> { + pub fn add_mapping(&mut self, mapping: Mapping) -> Result<()> { // Retrieve the mapping name. let mapping_name = *mapping.name(); @@ -361,7 +361,7 @@ impl, Command: CommandTrait> Pro /// This method will halt if the struct name is a reserved opcode or keyword. /// This method will halt if any structs in the struct's members are not already defined. #[inline] - fn add_struct(&mut self, struct_: StructType) -> Result<()> { + pub fn add_struct(&mut self, struct_: StructType) -> Result<()> { // Retrieve the struct name. let struct_name = *struct_.name(); @@ -422,7 +422,7 @@ impl, Command: CommandTrait> Pro /// This method will halt if the record name is a reserved opcode or keyword. /// This method will halt if any records in the record's members are not already defined. #[inline] - fn add_record(&mut self, record: RecordType) -> Result<()> { + pub fn add_record(&mut self, record: RecordType) -> Result<()> { // Retrieve the record name. let record_name = *record.name(); @@ -485,7 +485,7 @@ impl, Command: CommandTrait> Pro /// This method will halt if an output register does not already exist. /// This method will halt if an output type references a non-existent definition. #[inline] - fn add_closure(&mut self, closure: ClosureCore) -> Result<()> { + pub fn add_closure(&mut self, closure: ClosureCore) -> Result<()> { // Retrieve the closure name. let closure_name = *closure.name(); @@ -533,7 +533,7 @@ impl, Command: CommandTrait> Pro /// This method will halt if an output register does not already exist. /// This method will halt if an output type references a non-existent definition. #[inline] - fn add_function(&mut self, function: FunctionCore) -> Result<()> { + pub fn add_function(&mut self, function: FunctionCore) -> Result<()> { // Retrieve the function name. let function_name = *function.name(); From e9ddc887912d5aaacf0df824cfaa8b1ccdffc5be Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:51:40 -0800 Subject: [PATCH 24/46] Valid update tests --- synthesizer/process/src/tests/test_update.rs | 222 ++++++++++++++++--- synthesizer/program/src/lib.rs | 91 +++++++- 2 files changed, 284 insertions(+), 29 deletions(-) diff --git a/synthesizer/process/src/tests/test_update.rs b/synthesizer/process/src/tests/test_update.rs index 3172a11b01..cec3fdc800 100644 --- a/synthesizer/process/src/tests/test_update.rs +++ b/synthesizer/process/src/tests/test_update.rs @@ -13,38 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - CallStack, - Process, - Stack, - Trace, - traits::{StackEvaluate, StackExecute}, -}; -use circuit::{Aleo, network::AleoV0}; +use crate::Process; use console::{ - account::{Address, PrivateKey, ViewKey}, network::{MainnetV0, prelude::*}, - program::{Identifier, Literal, Plaintext, ProgramID, Record, Value}, - types::{Field, U64}, -}; -use ledger_block::{Fee, Transaction}; -use ledger_query::Query; -use ledger_store::{ - BlockStorage, - BlockStore, - FinalizeStorage, - FinalizeStore, - helpers::memory::{BlockMemory, FinalizeMemory}, + program::{Identifier, RecordType, StructType}, }; -use synthesizer_program::{FinalizeGlobalState, FinalizeStoreTrait, Import, Program, StackProgram}; -use synthesizer_snark::UniversalSRS; - -use indexmap::IndexMap; -use parking_lot::RwLock; -use std::sync::Arc; +use synthesizer_program::{Closure, Function, Import, Mapping, Program, StackProgram}; type CurrentNetwork = MainnetV0; -type CurrentAleo = AleoV0; /// Samples the default program to test updates on. fn default_program() -> Program { @@ -112,7 +88,7 @@ fn sample_process() -> Result> { } #[test] -fn test_update_with_additional_import() -> Result<()> { +fn test_add_import() -> Result<()> { // Sample the default process. let mut process = sample_process()?; // Add a dummy program to the process. @@ -131,3 +107,193 @@ fn test_update_with_additional_import() -> Result<()> { assert_eq!(stack.program().imports().len(), 2); Ok(()) } + +#[test] +fn test_add_struct() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new struct. + new_program.add_struct(StructType::from_str("struct foo:data as u8;")?)?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().structs().len(), 2); + Ok(()) +} + +#[test] +fn test_add_record() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new record. + new_program.add_record(RecordType::from_str("record foo:owner as address.private;data as u8.private;")?)?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().records().len(), 2); + Ok(()) +} + +#[test] +fn test_add_mapping() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new mapping. + new_program.add_mapping(Mapping::from_str("mapping foo:key as u8.public;value as u8.public;")?)?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().mappings().len(), 2); + Ok(()) +} + +#[test] +fn test_add_closure() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new closure. + new_program.add_closure(Closure::from_str( + "closure foo:input r0 as u8;input r1 as u8;add r0 r1 into r2;output r2 as u8;", + )?)?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().closures().len(), 2); + Ok(()) +} + +#[test] +fn test_add_function() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new function. + new_program.add_function(Function::from_str( + "function foo:input r0 as u8.private;input r1 as u8.private;add r0 r1 into r2;output r2 as u8.private;", + )?)?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().functions().len(), 4); + Ok(()) +} + +#[test] +fn test_modify_function() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Remove the `adder` function and add a new `adder` function. + new_program.remove_function(&Identifier::from_str("adder")?)?; + let new_function = Function::from_str( + "function adder:input r0 as u8.private;input r1 as u8.private;sub r0 r1 into r2;output r2 as u8.private;", + )?; + new_program.add_function(new_function.clone())?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().functions().len(), 3); + let updated_function = stack.program().get_function(&new_function.name())?; + assert_eq!(updated_function, new_function); + Ok(()) +} + +#[test] +fn test_modify_finalize() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Remove the `store_data` function and add a new `store_data` function. + new_program.remove_function(&Identifier::from_str("store_data")?)?; + let new_function = Function::from_str( + r" +function store_data: + input r0 as u8.public; + input r1 as u8.public; + async store_data r0 r1 into r2; + output r2 as basic.aleo/store_data.future; + +finalize store_data: + input r0 as u8.public; + input r1 as u8.public; + assert.eq r0 r1;", + )?; + new_program.add_function(new_function.clone())?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().functions().len(), 3); + let updated_function = stack.program().get_function(&new_function.name())?; + assert_eq!(updated_function, new_function); + Ok(()) +} + +#[test] +fn test_add_call_to_non_async_transition() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add a program with a non-async transition. + let new_program = Program::from_str( + r" +program non_async.aleo; + +function foo: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private;", + )?; + process.add_program(&new_program)?; + // Get the default program. + let mut new_program = default_program(); + // Add an import of `non_async.aleo` to the default program. + new_program.add_import(Import::from_str("import non_async.aleo;")?)?; + // Remove the `adder` function and add a new `adder` function. + new_program.remove_function(&Identifier::from_str("adder")?)?; + let new_function = Function::from_str( + "function adder:input r0 as u8.private;input r1 as u8.private;call non_async.aleo/foo r0 r1 into r2;output r2 as u8.private;", + )?; + new_program.add_function(new_function.clone())?; + // Add the new program to the process. + process.add_program(&new_program)?; + // Check that the updated program is edition 1. + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + // Check that the update was successful. + let stack = process.get_stack("basic.aleo")?; + assert_eq!(stack.program().functions().len(), 3); + let updated_function = stack.program().get_function(&new_function.name())?; + assert_eq!(updated_function, new_function); + Ok(()) +} diff --git a/synthesizer/program/src/lib.rs b/synthesizer/program/src/lib.rs index 6dec95cb1f..11e80bc1d1 100644 --- a/synthesizer/program/src/lib.rs +++ b/synthesizer/program/src/lib.rs @@ -93,7 +93,7 @@ use console::{ use indexmap::IndexMap; #[derive(Copy, Clone, PartialEq, Eq, Hash)] -enum ProgramDefinition { +pub enum ProgramDefinition { /// A program mapping. Mapping, /// A program struct. @@ -566,6 +566,95 @@ impl, Command: CommandTrait> Pro } } +impl, Command: CommandTrait> ProgramCore { + /// Removes an import from the program. + /// + /// # Errors + /// This method will halt if the imported program is not in the program. + #[inline] + pub fn remove_import(&mut self, program_id: &ProgramID) -> Result> { + match self.imports.shift_remove(program_id) { + Some(import) => Ok(import), + None => bail!("Import '{}' not found.", program_id), + } + } + + /// Removes a mapping from the program. + /// + /// # Errors + /// This method will halt if the mapping is not in the program. + #[inline] + pub fn remove_mapping(&mut self, mapping_name: &Identifier) -> Result> { + // Remove the mapping from `identifiers`. + self.identifiers.shift_remove(mapping_name); + // Remove and return the mapping. + match self.mappings.shift_remove(mapping_name) { + Some(mapping) => Ok(mapping), + None => bail!("Mapping '{}' not found.", mapping_name), + } + } + + /// Removes a struct from the program. + /// + /// # Errors + /// This method will halt if the struct is not in the program. + #[inline] + pub fn remove_struct(&mut self, struct_name: &Identifier) -> Result> { + // Remove the struct from `identifiers`. + self.identifiers.shift_remove(struct_name); + // Remove and return the struct. + match self.structs.shift_remove(struct_name) { + Some(struct_) => Ok(struct_), + None => bail!("Struct '{}' not found.", struct_name), + } + } + + /// Removes a record from the program. + /// + /// # Errors + /// This method will halt if the record is not in the program. + #[inline] + pub fn remove_record(&mut self, record_name: &Identifier) -> Result> { + // Remove the record from `identifiers`. + self.identifiers.shift_remove(record_name); + // Remove and return the record. + match self.records.shift_remove(record_name) { + Some(record) => Ok(record), + None => bail!("Record '{}' not found.", record_name), + } + } + + /// Removes a closure from the program. + /// + /// # Errors + /// This method will halt if the closure is not in the program. + #[inline] + pub fn remove_closure(&mut self, closure_name: &Identifier) -> Result> { + // Remove the closure from `identifiers`. + self.identifiers.shift_remove(closure_name); + // Remove and return the closure. + match self.closures.shift_remove(closure_name) { + Some(closure) => Ok(closure), + None => bail!("Closure '{}' not found.", closure_name), + } + } + + /// Removes a function from the program. + /// + /// # Errors + /// This method will halt if the function is not in the program. + #[inline] + pub fn remove_function(&mut self, function_name: &Identifier) -> Result> { + // Remove the function from `identifiers`. + self.identifiers.shift_remove(function_name); + // Remove and return the function. + match self.functions.shift_remove(function_name) { + Some(function) => Ok(function), + None => bail!("Function '{}' not found.", function_name), + } + } +} + impl, Command: CommandTrait> ProgramCore { #[rustfmt::skip] const KEYWORDS: &'static [&'static str] = &[ From 8b0dabaea6957ee17d0110b9df244e9d101f4742 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 7 Feb 2025 11:59:46 -0800 Subject: [PATCH 25/46] Add negative update tests --- ledger/src/tests.rs | 2 - .../process/src/stack/helpers/check_update.rs | 26 +- synthesizer/process/src/tests/test_update.rs | 306 +++++++++++++++++- 3 files changed, 316 insertions(+), 18 deletions(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 98eaebfb95..d3aacbd33c 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -2576,8 +2576,6 @@ function foo: } } - - // These tests require the proof targets to be low enough to be able to generate **valid** solutions. // This requires the 'test' feature to be enabled for the `console` dependency. #[cfg(feature = "test")] diff --git a/synthesizer/process/src/stack/helpers/check_update.rs b/synthesizer/process/src/stack/helpers/check_update.rs index b18255efdb..2be8620f3a 100644 --- a/synthesizer/process/src/stack/helpers/check_update.rs +++ b/synthesizer/process/src/stack/helpers/check_update.rs @@ -82,19 +82,19 @@ impl Stack { function.outputs() == new_function.outputs(), "Cannot update '{program_id}' because the outputs of the function '{function_name}' do not match" ); - if let Some(finalize) = function.finalize_logic() { - match new_function.finalize_logic() { - Some(new_finalize) => { - ensure!( - finalize.inputs() == new_finalize.inputs(), - "Cannot update '{program_id}' because the finalize inputs to the function '{function_name}' do not match" - ); - } - None => { - bail!( - "Cannot update '{program_id}' because the function '{function_name}' is missing a finalize block" - ) - } + match (function.finalize_logic(), new_function.finalize_logic()) { + (None, None) => {} // Do nothing + (None, Some(_)) => bail!( + "Cannot update '{program_id}' because the function '{function_name}' should not have a finalize block" + ), + (Some(_), None) => bail!( + "Cannot update '{program_id}' because the function '{function_name}' should have a finalize block" + ), + (Some(finalize), Some(new_finalize)) => { + ensure!( + finalize.inputs() == new_finalize.inputs(), + "Cannot update '{program_id}' because the finalize inputs to the function '{function_name}' do not match" + ); } } } diff --git a/synthesizer/process/src/tests/test_update.rs b/synthesizer/process/src/tests/test_update.rs index cec3fdc800..337e01af7c 100644 --- a/synthesizer/process/src/tests/test_update.rs +++ b/synthesizer/process/src/tests/test_update.rs @@ -16,7 +16,7 @@ use crate::Process; use console::{ network::{MainnetV0, prelude::*}, - program::{Identifier, RecordType, StructType}, + program::{Identifier, ProgramID, RecordType, StructType}, }; use synthesizer_program::{Closure, Function, Import, Mapping, Program, StackProgram}; @@ -203,7 +203,7 @@ fn test_add_function() -> Result<()> { } #[test] -fn test_modify_function() -> Result<()> { +fn test_modify_function_logic() -> Result<()> { // Sample the default process. let mut process = sample_process()?; // Get the default program. @@ -227,7 +227,25 @@ fn test_modify_function() -> Result<()> { } #[test] -fn test_modify_finalize() -> Result<()> { +fn test_modify_function_signature() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Remove the `adder` function and add a new `adder` function. + new_program.remove_function(&Identifier::from_str("adder")?)?; + // Modify the program to change the signature of the `adder` function. + let new_function = Function::from_str( + "function adder:input r0 as u16.private;input r1 as u16.private;add r0 r1 into r2;output r2 as u16.private;", + )?; + new_program.add_function(new_function.clone())?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_finalize_logic() -> Result<()> { // Sample the default process. let mut process = sample_process()?; // Get the default program. @@ -260,6 +278,193 @@ finalize store_data: Ok(()) } +#[test] +fn test_modify_finalize_signature() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Remove the `store_data` function and add a new `store_data` function. + new_program.remove_function(&Identifier::from_str("store_data")?)?; + let new_function = Function::from_str( + r" +function store_data: + input r0 as u8.public; + input r1 as u8.public; + async store_data 0u16 1u16 into r2; + output r2 as basic.aleo/store_data.future; + +finalize store_data: + input r0 as u16.public; + input r1 as u16.public; + assert.eq r0 r1;", + )?; + new_program.add_function(new_function.clone())?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_struct() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new struct. + new_program.remove_struct(&Identifier::from_str("bundle")?)?; + new_program.add_struct(StructType::from_str("struct bundle:first as u8;second as u8;third as u8;")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_record() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new record. + new_program.remove_record(&Identifier::from_str("data")?)?; + new_program.add_record(RecordType::from_str( + "record data:owner as address.private;data as bundle.private;counter as u8.private;", + )?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_mapping() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to add a new mapping. + new_program.remove_mapping(&Identifier::from_str("onchain")?)?; + new_program.add_mapping(Mapping::from_str("mapping onchain:key as u8.public;value as u16.public;")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_closure_logic() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Remove the `sum` closure and add a new `sum` closure. + new_program.remove_closure(&Identifier::from_str("sum")?)?; + // Modify the `sum` closure to add a new instruction. + let new_closure = Closure::from_str( + "closure sum:input r0 as u8;input r1 as u8;add r0 r1 into r2;sub r2 r1 into r3;output r3 as u8;", + )?; + new_program.add_closure(new_closure.clone())?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_closure_signature() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Remove the `sum` closure and add a new `sum` closure. + new_program.remove_closure(&Identifier::from_str("sum")?)?; + // Modify the `sum` closure to add a new instruction. + let new_closure = + Closure::from_str("closure sum:input r0 as u16;input r1 as u16;add r0 r1 into r2;output r2 as u16;")?; + new_program.add_closure(new_closure.clone())?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_import() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add a dummy program to the process. + let dummy_program = Program::from_str("program dummy.aleo;function foo:")?; + process.add_program(&dummy_program)?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to remove an import. + new_program.remove_import(&ProgramID::from_str("credits.aleo")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_struct() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to remove a struct. + new_program.remove_struct(&Identifier::from_str("bundle")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_record() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to remove a record. + new_program.remove_record(&Identifier::from_str("data")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_mapping() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to remove a mapping. + new_program.remove_mapping(&Identifier::from_str("onchain")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_closure() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to remove a closure. + new_program.remove_closure(&Identifier::from_str("sum")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_function() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Get the default program. + let mut new_program = default_program(); + // Modify the program to remove a function. + new_program.remove_function(&Identifier::from_str("adder")?)?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + #[test] fn test_add_call_to_non_async_transition() -> Result<()> { // Sample the default process. @@ -297,3 +502,98 @@ function foo: assert_eq!(updated_function, new_function); Ok(()) } + +#[test] +fn test_add_call_to_async_transition() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add a program with a non-async transition. + let new_program = Program::from_str( + r" +program async_example.aleo; + +function foo: + input r0 as u8.private; + input r1 as u8.private; + async foo r0 r1 into r2; + add r0 r1 into r3; + output r3 as u8.private; + output r2 as async_example.aleo/foo.future; +finalize foo: + input r0 as u8.public; + input r1 as u8.public; + assert.eq r0 r1;", + )?; + process.add_program(&new_program)?; + // Get the default program. + let mut new_program = default_program(); + // Add an import of `non_async.aleo` to the default program. + new_program.add_import(Import::from_str("import async_example.aleo;")?)?; + // Remove the `adder` function and add a new `adder` function. + new_program.remove_function(&Identifier::from_str("adder")?)?; + let new_function = Function::from_str( + r" +function adder: + input r0 as u8.private; + input r1 as u8.private; + call async_example.aleo/foo r0 r1 into r2 r3; + async adder r3 into r4; + output r2 as u8.private; + output r4 as basic.aleo/adder.future; +finalize adder: + input r0 as async_example.aleo/foo.future; + await r0;", + )?; + new_program.add_function(new_function.clone())?; + // Verify that the update was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_add_import_cycle() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + + // Verify that self-import cycles are not allowed. + let mut new_program = default_program(); + new_program.add_import(Import::from_str("import basic.aleo;")?)?; + process.add_program(&new_program)?; + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + + // Add a program dependent on `basic.aleo`. + let dependent_program = Program::from_str( + r" +import basic.aleo; +program dependent.aleo; +function foo: + input r0 as u8.private; + input r1 as u8.private; + call basic.aleo/adder r0 r1 into r2; + output r2 as u8.private;", + )?; + process.add_program(&dependent_program)?; + assert_eq!(process.get_stack("dependent.aleo")?.edition(), 0); + + // Update basic.aleo to import dependent.aleo. + // This is allowed since we do not do cycle detection across programs. + new_program.add_import(Import::from_str("import dependent.aleo;")?)?; + process.add_program(&new_program)?; + assert_eq!(process.get_stack("basic.aleo")?.edition(), 2); + + // Update basic.aleo's adder to call dependent.aleo/foo. + new_program.remove_function(&Identifier::from_str("adder")?)?; + let new_function = Function::from_str( + r" +function adder: + input r0 as u8.private; + input r1 as u8.private; + call dependent.aleo/foo r0 r1 into r2; + output r2 as u8.private;", + )?; + new_program.add_function(new_function.clone())?; + process.add_program(&new_program)?; + assert_eq!(process.get_stack("basic.aleo")?.edition(), 3); + + Ok(()) +} From ef44d0546654b73cb176cd6a854af81deeeff4f5 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:36:53 -0800 Subject: [PATCH 26/46] Add safety check against self imports on update --- synthesizer/process/src/stack/helpers/initialize.rs | 2 ++ synthesizer/process/src/tests/test_update.rs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index bce4dab05c..73a200e1e4 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -38,6 +38,8 @@ impl Stack { // Add all the imports into the stack. for import in program.imports().keys() { + // Ensure that the program does not import itself. + ensure!(import != program.id(), "Program cannot import itself"); // Ensure the program imports all exist in the process already. if !process.contains_program(import) { bail!("Cannot add program '{}' because its import '{import}' must be added first", program.id()) diff --git a/synthesizer/process/src/tests/test_update.rs b/synthesizer/process/src/tests/test_update.rs index 337e01af7c..3f0067e5ca 100644 --- a/synthesizer/process/src/tests/test_update.rs +++ b/synthesizer/process/src/tests/test_update.rs @@ -558,8 +558,7 @@ fn test_add_import_cycle() -> Result<()> { // Verify that self-import cycles are not allowed. let mut new_program = default_program(); new_program.add_import(Import::from_str("import basic.aleo;")?)?; - process.add_program(&new_program)?; - assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); + assert!(process.add_program(&new_program).is_err()); // Add a program dependent on `basic.aleo`. let dependent_program = Program::from_str( @@ -577,9 +576,10 @@ function foo: // Update basic.aleo to import dependent.aleo. // This is allowed since we do not do cycle detection across programs. + let mut new_program = default_program(); new_program.add_import(Import::from_str("import dependent.aleo;")?)?; process.add_program(&new_program)?; - assert_eq!(process.get_stack("basic.aleo")?.edition(), 2); + assert_eq!(process.get_stack("basic.aleo")?.edition(), 1); // Update basic.aleo's adder to call dependent.aleo/foo. new_program.remove_function(&Identifier::from_str("adder")?)?; @@ -593,7 +593,7 @@ function adder: )?; new_program.add_function(new_function.clone())?; process.add_program(&new_program)?; - assert_eq!(process.get_stack("basic.aleo")?.edition(), 3); + assert_eq!(process.get_stack("basic.aleo")?.edition(), 2); Ok(()) } From d6df694d8f49d88e20220ac60d85bd9ef64bf123 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:59:53 -0800 Subject: [PATCH 27/46] Refactor tests --- synthesizer/src/vm/mod.rs | 1738 +---------------------- synthesizer/src/vm/tests/mod.rs | 37 + synthesizer/src/vm/tests/test_update.rs | 14 + synthesizer/src/vm/tests/test_vm.rs | 1736 ++++++++++++++++++++++ 4 files changed, 1791 insertions(+), 1734 deletions(-) create mode 100644 synthesizer/src/vm/tests/mod.rs create mode 100644 synthesizer/src/vm/tests/test_update.rs create mode 100644 synthesizer/src/vm/tests/test_vm.rs diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 5da9cf609a..36063445a7 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -22,6 +22,9 @@ mod execute; mod finalize; mod verify; +#[cfg(test)] +mod tests; + use crate::{Restrictions, cast_mut_ref, cast_ref, convert, process}; use console::{ account::{Address, PrivateKey}, @@ -470,21 +473,19 @@ pub(crate) mod test_helpers { use console::{ account::{Address, ViewKey}, network::MainnetV0, - program::{Entry, Value}, + program::Value, types::Field, }; use ledger_block::{Block, Header, Metadata, Transition}; use ledger_store::helpers::memory::ConsensusMemory; #[cfg(feature = "rocks")] use ledger_store::helpers::rocksdb::ConsensusDB; - use ledger_test_helpers::{large_transaction_program, small_transaction_program}; use synthesizer_program::Program; use indexmap::IndexMap; use once_cell::sync::OnceCell; #[cfg(feature = "rocks")] use std::path::Path; - use synthesizer_snark::VerifyingKey; pub(crate) type CurrentNetwork = MainnetV0; @@ -845,1735 +846,4 @@ function compute: rng, ) } - - #[test] - fn test_multiple_deployments_and_multiple_executions() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - - // Select a record to spend. - let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Split once. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "split"), - [Value::Record(record), Value::from_str("1000000000u64").unwrap()].iter(), // 1000 credits - None, - 0, - None, - rng, - ) - .unwrap(); - let records = transaction.records().collect_vec(); - let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); - let second_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - vm.add_next_block(&block).unwrap(); - - // Split again. - let mut transactions = Vec::new(); - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "split"), - [Value::Record(first_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits - None, - 0, - None, - rng, - ) - .unwrap(); - let records = transaction.records().collect_vec(); - let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); - let third_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); - transactions.push(transaction); - // Split again. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "split"), - [Value::Record(second_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits - None, - 0, - None, - rng, - ) - .unwrap(); - let records = transaction.records().collect_vec(); - let second_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); - let fourth_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); - transactions.push(transaction); - // Add the split transactions to a block and update the VM. - let fee_block = sample_next_block(&vm, &caller_private_key, &transactions, rng).unwrap(); - vm.add_next_block(&fee_block).unwrap(); - - // Deploy the programs. - let first_program = r" -program test_program_1.aleo; -mapping map_0: - key as field.public; - value as field.public; -function init: - async init into r0; - output r0 as test_program_1.aleo/init.future; -finalize init: - set 0field into map_0[0field]; -function getter: - async getter into r0; - output r0 as test_program_1.aleo/getter.future; -finalize getter: - get map_0[0field] into r0; - "; - let second_program = r" -program test_program_2.aleo; -mapping map_0: - key as field.public; - value as field.public; -function init: - async init into r0; - output r0 as test_program_2.aleo/init.future; -finalize init: - set 0field into map_0[0field]; -function getter: - async getter into r0; - output r0 as test_program_2.aleo/getter.future; -finalize getter: - get map_0[0field] into r0; - "; - let first_deployment = vm - .deploy(&caller_private_key, &Program::from_str(first_program).unwrap(), Some(first_record), 1, None, rng) - .unwrap(); - let second_deployment = vm - .deploy(&caller_private_key, &Program::from_str(second_program).unwrap(), Some(second_record), 1, None, rng) - .unwrap(); - let deployment_block = - sample_next_block(&vm, &caller_private_key, &[first_deployment, second_deployment], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Execute the programs. - let first_execution = vm - .execute( - &caller_private_key, - ("test_program_1.aleo", "init"), - Vec::>::new().iter(), - Some(third_record), - 1, - None, - rng, - ) - .unwrap(); - let second_execution = vm - .execute( - &caller_private_key, - ("test_program_2.aleo", "init"), - Vec::>::new().iter(), - Some(fourth_record), - 1, - None, - rng, - ) - .unwrap(); - let execution_block = - sample_next_block(&vm, &caller_private_key, &[first_execution, second_execution], rng).unwrap(); - vm.add_next_block(&execution_block).unwrap(); - } - - #[test] - fn test_load_deployments_with_imports() { - // NOTE: This seed was chosen for the CI's RNG to ensure that the test passes. - let rng = &mut TestRng::fixed(123456789); - - // Initialize a new caller. - let caller_private_key = PrivateKey::::new(rng).unwrap(); - let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); - - // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); - // Initialize the genesis block. - let genesis = vm.genesis_beacon(&caller_private_key, rng).unwrap(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - let record_0 = records[0].1.decrypt(&caller_view_key).unwrap(); - let record_1 = records[1].1.decrypt(&caller_view_key).unwrap(); - let record_2 = records[2].1.decrypt(&caller_view_key).unwrap(); - let record_3 = records[3].1.decrypt(&caller_view_key).unwrap(); - - // Create the deployment for the first program. - let program_1 = r" -program first_program.aleo; - -function c: - input r0 as u8.private; - input r1 as u8.private; - add r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_1 = vm - .deploy(&caller_private_key, &Program::from_str(program_1).unwrap(), Some(record_0), 0, None, rng) - .unwrap(); - - // Deploy the first program. - let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_1.clone()], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Create the deployment for the second program. - let program_2 = r" -import first_program.aleo; - -program second_program.aleo; - -function b: - input r0 as u8.private; - input r1 as u8.private; - call first_program.aleo/c r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_2 = vm - .deploy(&caller_private_key, &Program::from_str(program_2).unwrap(), Some(record_1), 0, None, rng) - .unwrap(); - - // Deploy the second program. - let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_2.clone()], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Create the deployment for the third program. - let program_3 = r" -import second_program.aleo; - -program third_program.aleo; - -function a: - input r0 as u8.private; - input r1 as u8.private; - call second_program.aleo/b r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_3 = vm - .deploy(&caller_private_key, &Program::from_str(program_3).unwrap(), Some(record_2), 0, None, rng) - .unwrap(); - - // Create the deployment for the fourth program. - let program_4 = r" -import second_program.aleo; -import first_program.aleo; - -program fourth_program.aleo; - -function a: - input r0 as u8.private; - input r1 as u8.private; - call second_program.aleo/b r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_4 = vm - .deploy(&caller_private_key, &Program::from_str(program_4).unwrap(), Some(record_3), 0, None, rng) - .unwrap(); - - // Deploy the third and fourth program together. - let deployment_block = - sample_next_block(&vm, &caller_private_key, &[deployment_3.clone(), deployment_4.clone()], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Sanity check the ordering of the deployment transaction IDs from storage. - { - let deployment_transaction_ids = - vm.transaction_store().deployment_transaction_ids().map(|id| *id).collect::>(); - // This assert check is here to ensure that we are properly loading imports even though any order will work for `VM::from`. - // Note: `deployment_transaction_ids` is sorted lexicographically by transaction ID, so the order may change if we update internal methods. - assert_eq!( - deployment_transaction_ids, - vec![deployment_4.id(), deployment_3.id(), deployment_2.id(), deployment_1.id()], - "Update me if serialization has changed" - ); - } - - // Enforce that the VM can load properly with the imports. - assert!(VM::from(vm.store.clone()).is_ok()); - } - - #[test] - fn test_multiple_external_calls() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); - let address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Fetch the unspent records. - let records = - genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - let record_0 = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); - let record_1 = records.values().nth(1).unwrap().decrypt(&caller_view_key).unwrap(); - let record_2 = records.values().nth(2).unwrap().decrypt(&caller_view_key).unwrap(); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the program. - let program = Program::from_str( - r" -import credits.aleo; - -program test_multiple_external_calls.aleo; - -function multitransfer: - input r0 as credits.aleo/credits.record; - input r1 as address.private; - input r2 as u64.private; - call credits.aleo/transfer_private r0 r1 r2 into r3 r4; - call credits.aleo/transfer_private r4 r1 r2 into r5 r6; - output r4 as credits.aleo/credits.record; - output r5 as credits.aleo/credits.record; - output r6 as credits.aleo/credits.record; - ", - ) - .unwrap(); - let deployment = vm.deploy(&caller_private_key, &program, Some(record_0), 1, None, rng).unwrap(); - vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap()).unwrap(); - - // Execute the programs. - let inputs = [ - Value::::Record(record_1), - Value::::from_str(&address.to_string()).unwrap(), - Value::::from_str("10u64").unwrap(), - ]; - let execution = vm - .execute( - &caller_private_key, - ("test_multiple_external_calls.aleo", "multitransfer"), - inputs.into_iter(), - Some(record_2), - 1, - None, - rng, - ) - .unwrap(); - vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[execution], rng).unwrap()).unwrap(); - } - - #[test] - fn test_nested_deployment_with_assert() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program child_program.aleo; - -function check: - input r0 as field.private; - assert.eq r0 123456789123456789123456789123456789123456789123456789field; - ", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Check that program is deployed. - assert!(vm.contains_program(&ProgramID::from_str("child_program.aleo").unwrap())); - - // Deploy the program that calls the program from the previous layer. - let program = Program::from_str( - r" -import child_program.aleo; - -program parent_program.aleo; - -function check: - input r0 as field.private; - call child_program.aleo/check r0; - ", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Check that program is deployed. - assert!(vm.contains_program(&ProgramID::from_str("parent_program.aleo").unwrap())); - } - - #[test] - fn test_deployment_with_external_records() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the program. - let program = Program::from_str( - r" -import credits.aleo; -program test_program.aleo; - -function transfer: - input r0 as credits.aleo/credits.record; - input r1 as u64.private; - input r2 as u64.private; - input r3 as [address; 10u32].private; - call credits.aleo/transfer_private r0 r3[0u32] r1 into r4 r5; - call credits.aleo/transfer_private r5 r3[0u32] r2 into r6 r7; -", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Check that program is deployed. - assert!(vm.contains_program(&ProgramID::from_str("test_program.aleo").unwrap())); - } - - #[test] - fn test_internal_fee_calls_are_invalid() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - let view_key = ViewKey::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Fetch the unspent records. - let records = - genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - let record_0 = records.values().next().unwrap().decrypt(&view_key).unwrap(); - - // Deploy the program. - let program = Program::from_str( - r" -import credits.aleo; -program test_program.aleo; - -function call_fee_public: - input r0 as u64.private; - input r1 as u64.private; - input r2 as field.private; - call credits.aleo/fee_public r0 r1 r2 into r3; - async call_fee_public r3 into r4; - output r4 as test_program.aleo/call_fee_public.future; - -finalize call_fee_public: - input r0 as credits.aleo/fee_public.future; - await r0; - -function call_fee_private: - input r0 as credits.aleo/credits.record; - input r1 as u64.private; - input r2 as u64.private; - input r3 as field.private; - call credits.aleo/fee_private r0 r1 r2 r3 into r4; - output r4 as credits.aleo/credits.record; -", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Execute the programs. - let internal_base_fee_amount: u64 = rng.gen_range(1..1000); - let internal_priority_fee_amount: u64 = rng.gen_range(1..1000); - - // Ensure that the transaction that calls `fee_public` internally cannot be generated. - let inputs = [ - Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), - Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), - Value::::from_str("1field").unwrap(), - ]; - assert!( - vm.execute(&private_key, ("test_program.aleo", "call_fee_public"), inputs.into_iter(), None, 0, None, rng) - .is_err() - ); - - // Ensure that the transaction that calls `fee_private` internally cannot be generated. - let inputs = [ - Value::::Record(record_0), - Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), - Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), - Value::::from_str("1field").unwrap(), - ]; - assert!( - vm.execute(&private_key, ("test_program.aleo", "call_fee_private"), inputs.into_iter(), None, 0, None, rng) - .is_err() - ); - } - - #[test] - #[ignore = "memory-intensive"] - fn test_deployment_synthesis_overload() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_overload.aleo; - -function do: - input r0 as [[u128; 32u32]; 2u32].private; - hash.sha3_256 r0 into r1 as field; - output r1 as field.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Verify the deployment transaction. It should fail because there are too many constraints. - assert!(vm.check_transaction(&deployment, None, rng).is_err()); - } - - #[test] - fn test_deployment_num_constant_overload() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_num_constants.aleo; -function do: - cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; - cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; - cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; - hash.bhp1024 r2 into r3 as u32; - output r3 as u32.private; -function do2: - cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; - cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; - cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; - hash.bhp1024 r2 into r3 as u32; - output r3 as u32.private;", - ) - .unwrap(); - - // Create the deployment transaction. - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Verify the deployment transaction. It should fail because there are too many constants. - let check_tx_res = vm.check_transaction(&deployment, None, rng); - assert!(check_tx_res.is_err()); - } - - #[test] - fn test_deployment_synthesis_overreport() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_overreport.aleo; - -function do: - input r0 as u32.private; - add r0 r0 into r1; - output r1 as u32.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Destructure the deployment transaction. - let Transaction::Deploy(_, program_owner, deployment, fee) = transaction else { - panic!("Expected a deployment transaction"); - }; - - // Increase the number of constraints in the verifying keys. - let mut vks_with_overreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { - let mut vk_deref = vk.deref().clone(); - vk_deref.circuit_info.num_constraints += 1; - let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); - vks_with_overreport.push((*id, (vk, cert.clone()))); - } - - // Each additional constraint costs 25 microcredits, so we need to increase the fee by 25 microcredits. - let required_fee = *fee.base_amount().unwrap() + 25; - // Authorize a new fee. - let fee_authorization = vm - .authorize_fee_public(&private_key, required_fee, 0, deployment.as_ref().to_deployment_id().unwrap(), rng) - .unwrap(); - // Compute the fee. - let fee = vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); - - // Create a new deployment transaction with the overreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); - let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); - - // Verify the deployment transaction. It should error when certificate checking for constraint count mismatch. - let res = vm.check_transaction(&adjusted_transaction, None, rng); - assert!(res.is_err()); - } - - #[test] - fn test_deployment_synthesis_underreport() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - let address = Address::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_underreport.aleo; - -function do: - input r0 as u32.private; - add r0 r0 into r1; - output r1 as u32.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Destructure the deployment transaction. - let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { - panic!("Expected a deployment transaction"); - }; - - // Decrease the number of constraints in the verifying keys. - let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { - let mut vk_deref = vk.deref().clone(); - vk_deref.circuit_info.num_constraints -= 2; - let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); - vks_with_underreport.push((*id, (vk, cert.clone()))); - } - - // Create a new deployment transaction with the underreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); - let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); - - // Verify the deployment transaction. It should error when enforcing the first constraint over the vk limit. - let result = vm.check_transaction(&adjusted_transaction, None, rng); - assert!(result.is_err()); - - // Create a standard transaction - // Prepare the inputs. - let inputs = [ - Value::::from_str(&address.to_string()).unwrap(), - Value::::from_str("1u64").unwrap(), - ] - .into_iter(); - - // Execute. - let transaction = - vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); - - // Check that the deployment transaction will be aborted if injected into a block. - let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); - - // Check that the block aborts the deployment transaction. - assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - } - - #[test] - fn test_deployment_variable_underreport() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - let address = Address::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_underreport.aleo; -function do: - input r0 as u32.private; - add r0 r0 into r1; - output r1 as u32.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Destructure the deployment transaction. - let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { - panic!("Expected a deployment transaction"); - }; - - // Decrease the number of reported variables in the verifying keys. - let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { - let vk = VerifyingKey::new(Arc::new(vk.deref().clone()), vk.num_variables() - 2); - vks_with_underreport.push((*id, (vk.clone(), cert.clone()))); - } - - // Create a new deployment transaction with the underreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); - let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); - - // Verify the deployment transaction. It should error when synthesizing the first variable over the vk limit. - let result = vm.check_transaction(&adjusted_transaction, None, rng); - assert!(result.is_err()); - - // Create a standard transaction - // Prepare the inputs. - let inputs = [ - Value::::from_str(&address.to_string()).unwrap(), - Value::::from_str("1u64").unwrap(), - ] - .into_iter(); - - // Execute. - let transaction = - vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); - - // Check that the deployment transaction will be aborted if injected into a block. - let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); - - // Check that the block aborts the deployment transaction. - assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - } - - #[test] - #[ignore] - fn test_deployment_memory_overload() { - const NUM_DEPLOYMENTS: usize = 32; - - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize a view key. - let view_key = ViewKey::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program program_layer_0.aleo; - -mapping m: - key as u8.public; - value as u32.public; - -function do: - input r0 as u32.public; - async do r0 into r1; - output r1 as program_layer_0.aleo/do.future; - -finalize do: - input r0 as u32.public; - set r0 into m[0u8];", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // For each layer, deploy a program that calls the program from the previous layer. - for i in 1..NUM_DEPLOYMENTS { - let mut program_string = String::new(); - // Add the import statements. - for j in 0..i { - program_string.push_str(&format!("import program_layer_{}.aleo;\n", j)); - } - // Add the program body. - program_string.push_str(&format!( - "program program_layer_{i}.aleo; - -mapping m: - key as u8.public; - value as u32.public; - -function do: - input r0 as u32.public; - call program_layer_{prev}.aleo/do r0 into r1; - async do r0 r1 into r2; - output r2 as program_layer_{i}.aleo/do.future; - -finalize do: - input r0 as u32.public; - input r1 as program_layer_{prev}.aleo/do.future; - await r1; - set r0 into m[0u8];", - prev = i - 1 - )); - // Construct the program. - let program = Program::from_str(&program_string).unwrap(); - - // Deploy the program. - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - } - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - - // Select a record to spend. - let record = Some(records.values().next().unwrap().decrypt(&view_key).unwrap()); - - // Prepare the inputs. - let inputs = [Value::::from_str("1u32").unwrap()].into_iter(); - - // Execute. - let transaction = - vm.execute(&private_key, ("program_layer_30.aleo", "do"), inputs, record, 0, None, rng).unwrap(); - - // Verify. - vm.check_transaction(&transaction, None, rng).unwrap(); - } - - #[test] - fn test_transfer_public_from_user() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Transfer credits from the caller to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "transfer_public"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_843_183, "Update me if the initial balance changes."); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); - } - - #[test] - fn test_transfer_public_as_signer_from_user() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Transfer credits from the caller to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "transfer_public_as_signer"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_843_163, "Update me if the initial balance changes."); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); - } - - #[test] - fn transfer_public_from_program() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. - let program = Program::from_str( - r" -import credits.aleo; -program credits_wrapper.aleo; - -function transfer_public: - input r0 as address.public; - input r1 as u64.public; - call credits.aleo/transfer_public r0 r1 into r2; - async transfer_public r2 into r3; - output r3 as credits_wrapper.aleo/transfer_public.future; - -finalize transfer_public: - input r0 as credits.aleo/transfer_public.future; - await r0; - ", - ) - .unwrap(); - - // Get the address of the wrapper program. - let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); - let wrapper_program_address = wrapper_program_id.to_address().unwrap(); - - // Deploy the wrapper program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Transfer credits from the caller to the `credits_wrapper` program. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "transfer_public"), - [Value::from_str(&format!("{wrapper_program_address}")).unwrap(), Value::from_str("1u64").unwrap()] - .iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_996_914_808, "Update me if the initial balance changes."); - - // Check the balance of the `credits_wrapper` program. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); - - // Transfer credits from the `credits_wrapper` program to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits_wrapper.aleo", "transfer_public"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_996_862_283, "Update me if the initial balance changes."); - - // Check the balance of the `credits_wrapper` program. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 0); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); - } - - #[test] - fn transfer_public_as_signer_from_program() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. - let program = Program::from_str( - r" -import credits.aleo; -program credits_wrapper.aleo; - -function transfer_public_as_signer: - input r0 as address.public; - input r1 as u64.public; - call credits.aleo/transfer_public_as_signer r0 r1 into r2; - async transfer_public_as_signer r2 into r3; - output r3 as credits_wrapper.aleo/transfer_public_as_signer.future; - -finalize transfer_public_as_signer: - input r0 as credits.aleo/transfer_public_as_signer.future; - await r0; - ", - ) - .unwrap(); - - // Get the address of the wrapper program. - let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); - let wrapper_program_address = wrapper_program_id.to_address().unwrap(); - - // Deploy the wrapper program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Transfer credits from the signer using `credits_wrapper` program. - let transaction = vm - .execute( - &caller_private_key, - ("credits_wrapper.aleo", "transfer_public_as_signer"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_996_821_793, "Update me if the initial balance changes."); - - // Check the `credits_wrapper` program does not have any balance. - let balance = vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_address)), - ) - .unwrap(); - assert!(balance.is_none()); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); - } - - #[test] - fn test_transfer_public_to_private_from_program() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Check that the recipient does not have a public balance. - let balance = vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap(); - assert!(balance.is_none()); - - // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public_as_signer` then `transfer_public_to_private`. - let program = Program::from_str( - r" -import credits.aleo; - -program credits_wrapper.aleo; - -function transfer_public_to_private: - input r0 as address.private; - input r1 as u64.public; - call credits.aleo/transfer_public_as_signer credits_wrapper.aleo r1 into r2; - call credits.aleo/transfer_public_to_private r0 r1 into r3 r4; - async transfer_public_to_private r2 r4 into r5; - output r3 as credits.aleo/credits.record; - output r5 as credits_wrapper.aleo/transfer_public_to_private.future; - -finalize transfer_public_to_private: - input r0 as credits.aleo/transfer_public_as_signer.future; - input r1 as credits.aleo/transfer_public_to_private.future; - contains credits.aleo/account[credits_wrapper.aleo] into r2; - assert.eq r2 false; - await r0; - get credits.aleo/account[credits_wrapper.aleo] into r3; - assert.eq r3 r0[2u32]; - await r1; - ", - ) - .unwrap(); - - // Get the address of the wrapper program. - let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); - - // Deploy the wrapper program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Call the wrapper program to transfer credits from the caller to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits_wrapper.aleo", "transfer_public_to_private"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction.clone()], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - - assert_eq!(balance, 182_499_996_071_881, "Update me if the initial balance changes."); - - // Check that the `credits_wrapper` program has a balance of 0. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_id.to_address().unwrap())), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 0); - - // Check that the recipient does not have a public balance. - let balance = vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap(); - assert!(balance.is_none()); - - // Get the output record from the transaction and check that it is well-formed. - let records = transaction.records().collect_vec(); - assert_eq!(records.len(), 1); - let (commitment, record) = records[0]; - let record = record.decrypt(&ViewKey::try_from(&recipient_private_key).unwrap()).unwrap(); - assert_eq!(**record.owner(), recipient_address); - let data = record.data(); - assert_eq!(data.len(), 1); - match data.get(&Identifier::from_str("microcredits").unwrap()) { - Some(Entry::::Private(Plaintext::Literal(Literal::U64(value), _))) => { - assert_eq!(**value, 1) - } - _ => panic!("Incorrect record."), - } - - // Check that the record exists in the VM. - assert!(vm.transition_store().get_record(commitment).unwrap().is_some()); - - // Check that the serial number of the record does not exist in the VM. - assert!( - !vm.transition_store() - .contains_serial_number( - &Record::>::serial_number( - recipient_private_key, - *commitment - ) - .unwrap() - ) - .unwrap() - ); - } - - #[test] - fn test_large_transaction_is_aborted() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); - - // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy a program that produces small transactions. - let program = small_transaction_program(); - - // Deploy the program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Deploy a program that produces large transactions. - let program = large_transaction_program(); - - // Deploy the program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Call the program to produce the small transaction. - let transaction = vm - .execute( - &caller_private_key, - ("testing_small.aleo", "small_transaction"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Check that the transaction was accepted. - assert_eq!(block.transactions().num_accepted(), 1); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Call the program to produce a large transaction. - let transaction = vm - .execute( - &caller_private_key, - ("testing_large.aleo", "large_transaction"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify that the transaction is invalid. - assert!(vm.check_transaction(&transaction, None, rng).is_err()); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Check that the transaction was aborted. - assert_eq!(block.aborted_transaction_ids().len(), 1); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - } - - #[test] - fn test_vm_puzzle() { - // Attention: This test is used to ensure that the VM has performed downcasting correctly for - // the puzzle, and that the underlying traits in the puzzle are working correctly. Please - // *do not delete* this test as it is a critical safety check for the integrity of the - // instantiation of the puzzle in the VM. - - let rng = &mut TestRng::default(); - - // Initialize the VM. - let vm = sample_vm(); - - // Ensure this call succeeds. - vm.puzzle.prove(rng.gen(), rng.gen(), rng.gen(), None).unwrap(); - } - - #[cfg(feature = "rocks")] - #[test] - fn test_atomic_unpause_on_error() { - let rng = &mut TestRng::default(); - - // Initialize a genesis private key.. - let genesis_private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize a VM and sample 2 blocks using it. - let vm = sample_vm(); - vm.add_next_block(&genesis).unwrap(); - let block1 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); - vm.add_next_block(&block1).unwrap(); - let block2 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); - - // Create a new, rocks-based VM shadowing the 1st one. - let tempdir = tempfile::tempdir().unwrap(); - let vm = sample_vm_rocks(tempdir.path()); - vm.add_next_block(&genesis).unwrap(); - // This time, however, try to insert the 2nd block first, which fails due to height. - assert!(vm.add_next_block(&block2).is_err()); - - // It should still be possible to insert the 1st block afterwards. - vm.add_next_block(&block1).unwrap(); - } } diff --git a/synthesizer/src/vm/tests/mod.rs b/synthesizer/src/vm/tests/mod.rs new file mode 100644 index 0000000000..ac3e558946 --- /dev/null +++ b/synthesizer/src/vm/tests/mod.rs @@ -0,0 +1,37 @@ +// Copyright 2024 Aleo Network Foundation +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_update; +mod test_vm; + +use super::*; + +use crate::vm::test_helpers::*; + +use console::{ + account::{Address, ViewKey}, + network::MainnetV0, + program::{Entry, Value}, +}; +use ledger_block::Transition; +#[cfg(feature = "rocks")] +use ledger_store::helpers::rocksdb::ConsensusDB; +use ledger_test_helpers::{large_transaction_program, small_transaction_program}; +use synthesizer_program::Program; + +use indexmap::IndexMap; +#[cfg(feature = "rocks")] +use std::path::Path; +use synthesizer_snark::VerifyingKey; diff --git a/synthesizer/src/vm/tests/test_update.rs b/synthesizer/src/vm/tests/test_update.rs new file mode 100644 index 0000000000..137acd8f31 --- /dev/null +++ b/synthesizer/src/vm/tests/test_update.rs @@ -0,0 +1,14 @@ +// Copyright 2024 Aleo Network Foundation +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/synthesizer/src/vm/tests/test_vm.rs b/synthesizer/src/vm/tests/test_vm.rs new file mode 100644 index 0000000000..0962b1093e --- /dev/null +++ b/synthesizer/src/vm/tests/test_vm.rs @@ -0,0 +1,1736 @@ +// Copyright 2024 Aleo Network Foundation +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +#[test] +fn test_multiple_deployments_and_multiple_executions() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + + // Select a record to spend. + let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Split once. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "split"), + [Value::Record(record), Value::from_str("1000000000u64").unwrap()].iter(), // 1000 credits + None, + 0, + None, + rng, + ) + .unwrap(); + let records = transaction.records().collect_vec(); + let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); + let second_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + vm.add_next_block(&block).unwrap(); + + // Split again. + let mut transactions = Vec::new(); + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "split"), + [Value::Record(first_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits + None, + 0, + None, + rng, + ) + .unwrap(); + let records = transaction.records().collect_vec(); + let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); + let third_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); + transactions.push(transaction); + // Split again. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "split"), + [Value::Record(second_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits + None, + 0, + None, + rng, + ) + .unwrap(); + let records = transaction.records().collect_vec(); + let second_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); + let fourth_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); + transactions.push(transaction); + // Add the split transactions to a block and update the VM. + let fee_block = sample_next_block(&vm, &caller_private_key, &transactions, rng).unwrap(); + vm.add_next_block(&fee_block).unwrap(); + + // Deploy the programs. + let first_program = r" +program test_program_1.aleo; +mapping map_0: + key as field.public; + value as field.public; +function init: + async init into r0; + output r0 as test_program_1.aleo/init.future; +finalize init: + set 0field into map_0[0field]; +function getter: + async getter into r0; + output r0 as test_program_1.aleo/getter.future; +finalize getter: + get map_0[0field] into r0; + "; + let second_program = r" +program test_program_2.aleo; +mapping map_0: + key as field.public; + value as field.public; +function init: + async init into r0; + output r0 as test_program_2.aleo/init.future; +finalize init: + set 0field into map_0[0field]; +function getter: + async getter into r0; + output r0 as test_program_2.aleo/getter.future; +finalize getter: + get map_0[0field] into r0; + "; + let first_deployment = vm + .deploy(&caller_private_key, &Program::from_str(first_program).unwrap(), Some(first_record), 1, None, rng) + .unwrap(); + let second_deployment = vm + .deploy(&caller_private_key, &Program::from_str(second_program).unwrap(), Some(second_record), 1, None, rng) + .unwrap(); + let deployment_block = + sample_next_block(&vm, &caller_private_key, &[first_deployment, second_deployment], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Execute the programs. + let first_execution = vm + .execute( + &caller_private_key, + ("test_program_1.aleo", "init"), + Vec::>::new().iter(), + Some(third_record), + 1, + None, + rng, + ) + .unwrap(); + let second_execution = vm + .execute( + &caller_private_key, + ("test_program_2.aleo", "init"), + Vec::>::new().iter(), + Some(fourth_record), + 1, + None, + rng, + ) + .unwrap(); + let execution_block = + sample_next_block(&vm, &caller_private_key, &[first_execution, second_execution], rng).unwrap(); + vm.add_next_block(&execution_block).unwrap(); +} + +#[test] +fn test_load_deployments_with_imports() { + // NOTE: This seed was chosen for the CI's RNG to ensure that the test passes. + let rng = &mut TestRng::fixed(123456789); + + // Initialize a new caller. + let caller_private_key = PrivateKey::::new(rng).unwrap(); + let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + // Initialize the genesis block. + let genesis = vm.genesis_beacon(&caller_private_key, rng).unwrap(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + let record_0 = records[0].1.decrypt(&caller_view_key).unwrap(); + let record_1 = records[1].1.decrypt(&caller_view_key).unwrap(); + let record_2 = records[2].1.decrypt(&caller_view_key).unwrap(); + let record_3 = records[3].1.decrypt(&caller_view_key).unwrap(); + + // Create the deployment for the first program. + let program_1 = r" +program first_program.aleo; + +function c: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_1 = + vm.deploy(&caller_private_key, &Program::from_str(program_1).unwrap(), Some(record_0), 0, None, rng).unwrap(); + + // Deploy the first program. + let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_1.clone()], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Create the deployment for the second program. + let program_2 = r" +import first_program.aleo; + +program second_program.aleo; + +function b: + input r0 as u8.private; + input r1 as u8.private; + call first_program.aleo/c r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_2 = + vm.deploy(&caller_private_key, &Program::from_str(program_2).unwrap(), Some(record_1), 0, None, rng).unwrap(); + + // Deploy the second program. + let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_2.clone()], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Create the deployment for the third program. + let program_3 = r" +import second_program.aleo; + +program third_program.aleo; + +function a: + input r0 as u8.private; + input r1 as u8.private; + call second_program.aleo/b r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_3 = + vm.deploy(&caller_private_key, &Program::from_str(program_3).unwrap(), Some(record_2), 0, None, rng).unwrap(); + + // Create the deployment for the fourth program. + let program_4 = r" +import second_program.aleo; +import first_program.aleo; + +program fourth_program.aleo; + +function a: + input r0 as u8.private; + input r1 as u8.private; + call second_program.aleo/b r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_4 = + vm.deploy(&caller_private_key, &Program::from_str(program_4).unwrap(), Some(record_3), 0, None, rng).unwrap(); + + // Deploy the third and fourth program together. + let deployment_block = + sample_next_block(&vm, &caller_private_key, &[deployment_3.clone(), deployment_4.clone()], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Sanity check the ordering of the deployment transaction IDs from storage. + { + let deployment_transaction_ids = + vm.transaction_store().deployment_transaction_ids().map(|id| *id).collect::>(); + // This assert check is here to ensure that we are properly loading imports even though any order will work for `VM::from`. + // Note: `deployment_transaction_ids` is sorted lexicographically by transaction ID, so the order may change if we update internal methods. + assert_eq!( + deployment_transaction_ids, + vec![deployment_4.id(), deployment_3.id(), deployment_2.id(), deployment_1.id()], + "Update me if serialization has changed" + ); + } + + // Enforce that the VM can load properly with the imports. + assert!(VM::from(vm.store.clone()).is_ok()); +} + +#[test] +fn test_multiple_external_calls() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); + let address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + let record_0 = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); + let record_1 = records.values().nth(1).unwrap().decrypt(&caller_view_key).unwrap(); + let record_2 = records.values().nth(2).unwrap().decrypt(&caller_view_key).unwrap(); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the program. + let program = Program::from_str( + r" +import credits.aleo; + +program test_multiple_external_calls.aleo; + +function multitransfer: + input r0 as credits.aleo/credits.record; + input r1 as address.private; + input r2 as u64.private; + call credits.aleo/transfer_private r0 r1 r2 into r3 r4; + call credits.aleo/transfer_private r4 r1 r2 into r5 r6; + output r4 as credits.aleo/credits.record; + output r5 as credits.aleo/credits.record; + output r6 as credits.aleo/credits.record; + ", + ) + .unwrap(); + let deployment = vm.deploy(&caller_private_key, &program, Some(record_0), 1, None, rng).unwrap(); + vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap()).unwrap(); + + // Execute the programs. + let inputs = [ + Value::::Record(record_1), + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("10u64").unwrap(), + ]; + let execution = vm + .execute( + &caller_private_key, + ("test_multiple_external_calls.aleo", "multitransfer"), + inputs.into_iter(), + Some(record_2), + 1, + None, + rng, + ) + .unwrap(); + vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[execution], rng).unwrap()).unwrap(); +} + +#[test] +fn test_nested_deployment_with_assert() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program child_program.aleo; + +function check: + input r0 as field.private; + assert.eq r0 123456789123456789123456789123456789123456789123456789field; + ", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Check that program is deployed. + assert!(vm.contains_program(&ProgramID::from_str("child_program.aleo").unwrap())); + + // Deploy the program that calls the program from the previous layer. + let program = Program::from_str( + r" +import child_program.aleo; + +program parent_program.aleo; + +function check: + input r0 as field.private; + call child_program.aleo/check r0; + ", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Check that program is deployed. + assert!(vm.contains_program(&ProgramID::from_str("parent_program.aleo").unwrap())); +} + +#[test] +fn test_deployment_with_external_records() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the program. + let program = Program::from_str( + r" +import credits.aleo; +program test_program.aleo; + +function transfer: + input r0 as credits.aleo/credits.record; + input r1 as u64.private; + input r2 as u64.private; + input r3 as [address; 10u32].private; + call credits.aleo/transfer_private r0 r3[0u32] r1 into r4 r5; + call credits.aleo/transfer_private r5 r3[0u32] r2 into r6 r7; +", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Check that program is deployed. + assert!(vm.contains_program(&ProgramID::from_str("test_program.aleo").unwrap())); +} + +#[test] +fn test_internal_fee_calls_are_invalid() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let view_key = ViewKey::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + let record_0 = records.values().next().unwrap().decrypt(&view_key).unwrap(); + + // Deploy the program. + let program = Program::from_str( + r" +import credits.aleo; +program test_program.aleo; + +function call_fee_public: + input r0 as u64.private; + input r1 as u64.private; + input r2 as field.private; + call credits.aleo/fee_public r0 r1 r2 into r3; + async call_fee_public r3 into r4; + output r4 as test_program.aleo/call_fee_public.future; + +finalize call_fee_public: + input r0 as credits.aleo/fee_public.future; + await r0; + +function call_fee_private: + input r0 as credits.aleo/credits.record; + input r1 as u64.private; + input r2 as u64.private; + input r3 as field.private; + call credits.aleo/fee_private r0 r1 r2 r3 into r4; + output r4 as credits.aleo/credits.record; +", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Execute the programs. + let internal_base_fee_amount: u64 = rng.gen_range(1..1000); + let internal_priority_fee_amount: u64 = rng.gen_range(1..1000); + + // Ensure that the transaction that calls `fee_public` internally cannot be generated. + let inputs = [ + Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), + Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), + Value::::from_str("1field").unwrap(), + ]; + assert!( + vm.execute(&private_key, ("test_program.aleo", "call_fee_public"), inputs.into_iter(), None, 0, None, rng) + .is_err() + ); + + // Ensure that the transaction that calls `fee_private` internally cannot be generated. + let inputs = [ + Value::::Record(record_0), + Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), + Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), + Value::::from_str("1field").unwrap(), + ]; + assert!( + vm.execute(&private_key, ("test_program.aleo", "call_fee_private"), inputs.into_iter(), None, 0, None, rng) + .is_err() + ); +} + +#[test] +#[ignore = "memory-intensive"] +fn test_deployment_synthesis_overload() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_overload.aleo; + +function do: + input r0 as [[u128; 32u32]; 2u32].private; + hash.sha3_256 r0 into r1 as field; + output r1 as field.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Verify the deployment transaction. It should fail because there are too many constraints. + assert!(vm.check_transaction(&deployment, None, rng).is_err()); +} + +#[test] +fn test_deployment_num_constant_overload() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_num_constants.aleo; +function do: + cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; + cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; + hash.bhp1024 r2 into r3 as u32; + output r3 as u32.private; +function do2: + cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; + cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; + hash.bhp1024 r2 into r3 as u32; + output r3 as u32.private;", + ) + .unwrap(); + + // Create the deployment transaction. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Verify the deployment transaction. It should fail because there are too many constants. + let check_tx_res = vm.check_transaction(&deployment, None, rng); + assert!(check_tx_res.is_err()); +} + +#[test] +fn test_deployment_synthesis_overreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_overreport.aleo; + +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(_, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Increase the number of constraints in the verifying keys. + let mut vks_with_overreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert)) in deployment.verifying_keys() { + let mut vk_deref = vk.deref().clone(); + vk_deref.circuit_info.num_constraints += 1; + let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); + vks_with_overreport.push((*id, (vk, cert.clone()))); + } + + // Each additional constraint costs 25 microcredits, so we need to increase the fee by 25 microcredits. + let required_fee = *fee.base_amount().unwrap() + 25; + // Authorize a new fee. + let fee_authorization = vm + .authorize_fee_public(&private_key, required_fee, 0, deployment.as_ref().to_deployment_id().unwrap(), rng) + .unwrap(); + // Compute the fee. + let fee = vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); + + // Create a new deployment transaction with the overreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); + let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); + + // Verify the deployment transaction. It should error when certificate checking for constraint count mismatch. + let res = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(res.is_err()); +} + +#[test] +fn test_deployment_synthesis_underreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_underreport.aleo; + +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Decrease the number of constraints in the verifying keys. + let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert)) in deployment.verifying_keys() { + let mut vk_deref = vk.deref().clone(); + vk_deref.circuit_info.num_constraints -= 2; + let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); + vks_with_underreport.push((*id, (vk, cert.clone()))); + } + + // Create a new deployment transaction with the underreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + + // Verify the deployment transaction. It should error when enforcing the first constraint over the vk limit. + let result = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(result.is_err()); + + // Create a standard transaction + // Prepare the inputs. + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + + // Check that the deployment transaction will be aborted if injected into a block. + let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); + + // Check that the block aborts the deployment transaction. + assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); + + // Update the VM. + vm.add_next_block(&block).unwrap(); +} + +#[test] +fn test_deployment_variable_underreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_underreport.aleo; +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Decrease the number of reported variables in the verifying keys. + let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert)) in deployment.verifying_keys() { + let vk = VerifyingKey::new(Arc::new(vk.deref().clone()), vk.num_variables() - 2); + vks_with_underreport.push((*id, (vk.clone(), cert.clone()))); + } + + // Create a new deployment transaction with the underreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + + // Verify the deployment transaction. It should error when synthesizing the first variable over the vk limit. + let result = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(result.is_err()); + + // Create a standard transaction + // Prepare the inputs. + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + + // Check that the deployment transaction will be aborted if injected into a block. + let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); + + // Check that the block aborts the deployment transaction. + assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); + + // Update the VM. + vm.add_next_block(&block).unwrap(); +} + +#[test] +#[ignore] +fn test_deployment_memory_overload() { + const NUM_DEPLOYMENTS: usize = 32; + + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize a view key. + let view_key = ViewKey::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program program_layer_0.aleo; + +mapping m: + key as u8.public; + value as u32.public; + +function do: + input r0 as u32.public; + async do r0 into r1; + output r1 as program_layer_0.aleo/do.future; + +finalize do: + input r0 as u32.public; + set r0 into m[0u8];", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // For each layer, deploy a program that calls the program from the previous layer. + for i in 1..NUM_DEPLOYMENTS { + let mut program_string = String::new(); + // Add the import statements. + for j in 0..i { + program_string.push_str(&format!("import program_layer_{}.aleo;\n", j)); + } + // Add the program body. + program_string.push_str(&format!( + "program program_layer_{i}.aleo; + +mapping m: + key as u8.public; + value as u32.public; + +function do: + input r0 as u32.public; + call program_layer_{prev}.aleo/do r0 into r1; + async do r0 r1 into r2; + output r2 as program_layer_{i}.aleo/do.future; + +finalize do: + input r0 as u32.public; + input r1 as program_layer_{prev}.aleo/do.future; + await r1; + set r0 into m[0u8];", + prev = i - 1 + )); + // Construct the program. + let program = Program::from_str(&program_string).unwrap(); + + // Deploy the program. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + } + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + + // Select a record to spend. + let record = Some(records.values().next().unwrap().decrypt(&view_key).unwrap()); + + // Prepare the inputs. + let inputs = [Value::::from_str("1u32").unwrap()].into_iter(); + + // Execute. + let transaction = vm.execute(&private_key, ("program_layer_30.aleo", "do"), inputs, record, 0, None, rng).unwrap(); + + // Verify. + vm.check_transaction(&transaction, None, rng).unwrap(); +} + +#[test] +fn test_transfer_public_from_user() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Transfer credits from the caller to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_843_183, "Update me if the initial balance changes."); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); +} + +#[test] +fn test_transfer_public_as_signer_from_user() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Transfer credits from the caller to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public_as_signer"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_843_163, "Update me if the initial balance changes."); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); +} + +#[test] +fn transfer_public_from_program() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. + let program = Program::from_str( + r" +import credits.aleo; +program credits_wrapper.aleo; + +function transfer_public: + input r0 as address.public; + input r1 as u64.public; + call credits.aleo/transfer_public r0 r1 into r2; + async transfer_public r2 into r3; + output r3 as credits_wrapper.aleo/transfer_public.future; + +finalize transfer_public: + input r0 as credits.aleo/transfer_public.future; + await r0; + ", + ) + .unwrap(); + + // Get the address of the wrapper program. + let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); + let wrapper_program_address = wrapper_program_id.to_address().unwrap(); + + // Deploy the wrapper program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Transfer credits from the caller to the `credits_wrapper` program. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + [Value::from_str(&format!("{wrapper_program_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_996_914_808, "Update me if the initial balance changes."); + + // Check the balance of the `credits_wrapper` program. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); + + // Transfer credits from the `credits_wrapper` program to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits_wrapper.aleo", "transfer_public"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_996_862_283, "Update me if the initial balance changes."); + + // Check the balance of the `credits_wrapper` program. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 0); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); +} + +#[test] +fn transfer_public_as_signer_from_program() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. + let program = Program::from_str( + r" +import credits.aleo; +program credits_wrapper.aleo; + +function transfer_public_as_signer: + input r0 as address.public; + input r1 as u64.public; + call credits.aleo/transfer_public_as_signer r0 r1 into r2; + async transfer_public_as_signer r2 into r3; + output r3 as credits_wrapper.aleo/transfer_public_as_signer.future; + +finalize transfer_public_as_signer: + input r0 as credits.aleo/transfer_public_as_signer.future; + await r0; + ", + ) + .unwrap(); + + // Get the address of the wrapper program. + let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); + let wrapper_program_address = wrapper_program_id.to_address().unwrap(); + + // Deploy the wrapper program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Transfer credits from the signer using `credits_wrapper` program. + let transaction = vm + .execute( + &caller_private_key, + ("credits_wrapper.aleo", "transfer_public_as_signer"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_996_821_793, "Update me if the initial balance changes."); + + // Check the `credits_wrapper` program does not have any balance. + let balance = vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_address)), + ) + .unwrap(); + assert!(balance.is_none()); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); +} + +#[test] +fn test_transfer_public_to_private_from_program() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Check that the recipient does not have a public balance. + let balance = vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap(); + assert!(balance.is_none()); + + // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public_as_signer` then `transfer_public_to_private`. + let program = Program::from_str( + r" +import credits.aleo; + +program credits_wrapper.aleo; + +function transfer_public_to_private: + input r0 as address.private; + input r1 as u64.public; + call credits.aleo/transfer_public_as_signer credits_wrapper.aleo r1 into r2; + call credits.aleo/transfer_public_to_private r0 r1 into r3 r4; + async transfer_public_to_private r2 r4 into r5; + output r3 as credits.aleo/credits.record; + output r5 as credits_wrapper.aleo/transfer_public_to_private.future; + +finalize transfer_public_to_private: + input r0 as credits.aleo/transfer_public_as_signer.future; + input r1 as credits.aleo/transfer_public_to_private.future; + contains credits.aleo/account[credits_wrapper.aleo] into r2; + assert.eq r2 false; + await r0; + get credits.aleo/account[credits_wrapper.aleo] into r3; + assert.eq r3 r0[2u32]; + await r1; + ", + ) + .unwrap(); + + // Get the address of the wrapper program. + let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); + + // Deploy the wrapper program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Call the wrapper program to transfer credits from the caller to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits_wrapper.aleo", "transfer_public_to_private"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction.clone()], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + + assert_eq!(balance, 182_499_996_071_881, "Update me if the initial balance changes."); + + // Check that the `credits_wrapper` program has a balance of 0. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_id.to_address().unwrap())), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 0); + + // Check that the recipient does not have a public balance. + let balance = vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap(); + assert!(balance.is_none()); + + // Get the output record from the transaction and check that it is well-formed. + let records = transaction.records().collect_vec(); + assert_eq!(records.len(), 1); + let (commitment, record) = records[0]; + let record = record.decrypt(&ViewKey::try_from(&recipient_private_key).unwrap()).unwrap(); + assert_eq!(**record.owner(), recipient_address); + let data = record.data(); + assert_eq!(data.len(), 1); + match data.get(&Identifier::from_str("microcredits").unwrap()) { + Some(Entry::::Private(Plaintext::Literal(Literal::U64(value), _))) => { + assert_eq!(**value, 1) + } + _ => panic!("Incorrect record."), + } + + // Check that the record exists in the VM. + assert!(vm.transition_store().get_record(commitment).unwrap().is_some()); + + // Check that the serial number of the record does not exist in the VM. + assert!( + !vm.transition_store() + .contains_serial_number( + &Record::>::serial_number(recipient_private_key, *commitment) + .unwrap() + ) + .unwrap() + ); +} + +#[test] +fn test_large_transaction_is_aborted() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy a program that produces small transactions. + let program = small_transaction_program(); + + // Deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Deploy a program that produces large transactions. + let program = large_transaction_program(); + + // Deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Call the program to produce the small transaction. + let transaction = vm + .execute( + &caller_private_key, + ("testing_small.aleo", "small_transaction"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Check that the transaction was accepted. + assert_eq!(block.transactions().num_accepted(), 1); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Call the program to produce a large transaction. + let transaction = vm + .execute( + &caller_private_key, + ("testing_large.aleo", "large_transaction"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify that the transaction is invalid. + assert!(vm.check_transaction(&transaction, None, rng).is_err()); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Check that the transaction was aborted. + assert_eq!(block.aborted_transaction_ids().len(), 1); + + // Update the VM. + vm.add_next_block(&block).unwrap(); +} + +#[test] +fn test_vm_puzzle() { + // Attention: This test is used to ensure that the VM has performed downcasting correctly for + // the puzzle, and that the underlying traits in the puzzle are working correctly. Please + // *do not delete* this test as it is a critical safety check for the integrity of the + // instantiation of the puzzle in the VM. + + let rng = &mut TestRng::default(); + + // Initialize the VM. + let vm = sample_vm(); + + // Ensure this call succeeds. + vm.puzzle.prove(rng.gen(), rng.gen(), rng.gen(), None).unwrap(); +} + +#[cfg(feature = "rocks")] +#[test] +fn test_atomic_unpause_on_error() { + let rng = &mut TestRng::default(); + + // Initialize a genesis private key.. + let genesis_private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize a VM and sample 2 blocks using it. + let vm = sample_vm(); + vm.add_next_block(&genesis).unwrap(); + let block1 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); + vm.add_next_block(&block1).unwrap(); + let block2 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); + + // Create a new, rocks-based VM shadowing the 1st one. + let tempdir = tempfile::tempdir().unwrap(); + let vm = sample_vm_rocks(tempdir.path()); + vm.add_next_block(&genesis).unwrap(); + // This time, however, try to insert the 2nd block first, which fails due to height. + assert!(vm.add_next_block(&block2).is_err()); + + // It should still be possible to insert the 1st block afterwards. + vm.add_next_block(&block1).unwrap(); +} From 7bda11a6d2153b65499991f756b6cf7915240b4b Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:01:54 -0800 Subject: [PATCH 28/46] Clippy --- synthesizer/process/src/tests/test_update.rs | 6 +++--- synthesizer/src/vm/tests/mod.rs | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/synthesizer/process/src/tests/test_update.rs b/synthesizer/process/src/tests/test_update.rs index 3f0067e5ca..8557c780f4 100644 --- a/synthesizer/process/src/tests/test_update.rs +++ b/synthesizer/process/src/tests/test_update.rs @@ -221,7 +221,7 @@ fn test_modify_function_logic() -> Result<()> { // Check that the update was successful. let stack = process.get_stack("basic.aleo")?; assert_eq!(stack.program().functions().len(), 3); - let updated_function = stack.program().get_function(&new_function.name())?; + let updated_function = stack.program().get_function(new_function.name())?; assert_eq!(updated_function, new_function); Ok(()) } @@ -273,7 +273,7 @@ finalize store_data: // Check that the update was successful. let stack = process.get_stack("basic.aleo")?; assert_eq!(stack.program().functions().len(), 3); - let updated_function = stack.program().get_function(&new_function.name())?; + let updated_function = stack.program().get_function(new_function.name())?; assert_eq!(updated_function, new_function); Ok(()) } @@ -498,7 +498,7 @@ function foo: // Check that the update was successful. let stack = process.get_stack("basic.aleo")?; assert_eq!(stack.program().functions().len(), 3); - let updated_function = stack.program().get_function(&new_function.name())?; + let updated_function = stack.program().get_function(new_function.name())?; assert_eq!(updated_function, new_function); Ok(()) } diff --git a/synthesizer/src/vm/tests/mod.rs b/synthesizer/src/vm/tests/mod.rs index ac3e558946..4b7082a686 100644 --- a/synthesizer/src/vm/tests/mod.rs +++ b/synthesizer/src/vm/tests/mod.rs @@ -26,12 +26,8 @@ use console::{ program::{Entry, Value}, }; use ledger_block::Transition; -#[cfg(feature = "rocks")] -use ledger_store::helpers::rocksdb::ConsensusDB; use ledger_test_helpers::{large_transaction_program, small_transaction_program}; use synthesizer_program::Program; use indexmap::IndexMap; -#[cfg(feature = "rocks")] -use std::path::Path; use synthesizer_snark::VerifyingKey; From ef54427f8cd72e2d5c1293a78fc031b6a19fa6ab Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:10:30 -0800 Subject: [PATCH 29/46] Basic upgrade passes --- synthesizer/process/src/finalize.rs | 5 + .../process/src/stack/helpers/check_update.rs | 7 +- synthesizer/process/src/verify_deployment.rs | 5 - synthesizer/src/vm/deploy.rs | 8 +- synthesizer/src/vm/finalize.rs | 4 + synthesizer/src/vm/tests/mod.rs | 4 +- synthesizer/src/vm/tests/test_update.rs | 14 -- .../tests/{test_vm.rs => test_vm_standard.rs} | 46 +++--- synthesizer/src/vm/tests/test_vm_update.rs | 142 ++++++++++++++++++ 9 files changed, 183 insertions(+), 52 deletions(-) delete mode 100644 synthesizer/src/vm/tests/test_update.rs rename synthesizer/src/vm/tests/{test_vm.rs => test_vm_standard.rs} (97%) create mode 100644 synthesizer/src/vm/tests/test_vm_update.rs diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index c077833542..5eb962f16b 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -33,6 +33,7 @@ impl Process { deployment: &Deployment, fee: &Fee, ) -> Result<(Stack, Vec>)> { + println!("1"); let timer = timer!("Process::finalize_deployment"); // Compute the program stack. @@ -51,6 +52,8 @@ impl Process { } lap!(timer, "Insert the verifying keys"); + println!("2"); + // Determine which mappings must be initialized. let mappings = match deployment.edition().is_zero() { true => deployment.program().mappings().values().collect::>(), @@ -94,6 +97,8 @@ impl Process { } finish!(timer, "Initialize the program mappings"); + println!("3"); + // Return the stack and finalize operations. Ok((stack, finalize_operations)) }) diff --git a/synthesizer/process/src/stack/helpers/check_update.rs b/synthesizer/process/src/stack/helpers/check_update.rs index 2be8620f3a..b3a808954e 100644 --- a/synthesizer/process/src/stack/helpers/check_update.rs +++ b/synthesizer/process/src/stack/helpers/check_update.rs @@ -75,11 +75,11 @@ impl Stack { } let new_function = program.get_function(function.name())?; ensure!( - function.inputs() == new_function.inputs(), + function.input_types() == new_function.input_types(), "Cannot update '{program_id}' because the inputs to the function '{function_name}' do not match" ); ensure!( - function.outputs() == new_function.outputs(), + function.output_types() == new_function.output_types(), "Cannot update '{program_id}' because the outputs of the function '{function_name}' do not match" ); match (function.finalize_logic(), new_function.finalize_logic()) { @@ -92,13 +92,12 @@ impl Stack { ), (Some(finalize), Some(new_finalize)) => { ensure!( - finalize.inputs() == new_finalize.inputs(), + finalize.input_types() == new_finalize.input_types(), "Cannot update '{program_id}' because the finalize inputs to the function '{function_name}' do not match" ); } } } - Ok(()) } } diff --git a/synthesizer/process/src/verify_deployment.rs b/synthesizer/process/src/verify_deployment.rs index a4c83bebe2..78338696ca 100644 --- a/synthesizer/process/src/verify_deployment.rs +++ b/synthesizer/process/src/verify_deployment.rs @@ -25,11 +25,6 @@ impl Process { ) -> Result<()> { let timer = timer!("Process::verify_deployment"); - // Retrieve the program ID. - let program_id = deployment.program().id(); - // Ensure the program does not already exist in the process. - ensure!(!self.contains_program(program_id), "Program '{program_id}' already exists"); - // Ensure the program is well-formed, by computing the stack. let stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index efc92412be..31c12d3ee5 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -80,7 +80,7 @@ impl> VM { &self, private_key: &PrivateKey, authority: Address, - edition: U16, + edition: u16, program: &Program, fee_record: Option>>, priority_fee_in_microcredits: u64, @@ -91,17 +91,17 @@ impl> VM { let deployment = self.deploy_raw(program, rng)?; // Ensure that the deployment edition matches the expected edition. ensure!( - deployment.edition() == *edition, + deployment.edition() == edition, "The deployment edition ({}) does not match the expected edition ({})", deployment.edition(), - *edition + edition ); // Ensure the transaction is not empty. ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty transaction deployment"); // Compute the deployment ID. let deployment_id = deployment.to_deployment_id()?; // Construct the owner with authority. - let owner = ProgramOwner::new_v2(private_key, authority, edition, deployment_id, rng)?; + let owner = ProgramOwner::new_v2(private_key, authority, U16::new(edition), deployment_id, rng)?; // Compute the minimum deployment cost. let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 7e0ed3b170..8488ced8ed 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -60,6 +60,8 @@ impl> VM { false => self.prepare_for_speculate(&candidate_transactions, rng)?, }; + println!("Verification aborted transactions: {:?}", verification_aborted_transactions); + // Performs a **dry-run** over the list of ratifications, solutions, and transactions. let (ratifications, confirmed_transactions, speculation_aborted_transactions, ratified_finalize_operations) = self.atomic_speculate( @@ -71,6 +73,8 @@ impl> VM { verified_transactions.into_iter(), )?; + println!("Speculation aborted transactions: {:?}", speculation_aborted_transactions); + // Get the aborted transaction ids. let verification_aborted_transaction_ids = verification_aborted_transactions.iter().map(|(tx, e)| (tx.id(), e)); let speculation_aborted_transaction_ids = speculation_aborted_transactions.iter().map(|(tx, e)| (tx.id(), e)); diff --git a/synthesizer/src/vm/tests/mod.rs b/synthesizer/src/vm/tests/mod.rs index 4b7082a686..5765584760 100644 --- a/synthesizer/src/vm/tests/mod.rs +++ b/synthesizer/src/vm/tests/mod.rs @@ -13,8 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod test_update; -mod test_vm; +mod test_vm_standard; +mod test_vm_update; use super::*; diff --git a/synthesizer/src/vm/tests/test_update.rs b/synthesizer/src/vm/tests/test_update.rs deleted file mode 100644 index 137acd8f31..0000000000 --- a/synthesizer/src/vm/tests/test_update.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2024 Aleo Network Foundation -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. diff --git a/synthesizer/src/vm/tests/test_vm.rs b/synthesizer/src/vm/tests/test_vm_standard.rs similarity index 97% rename from synthesizer/src/vm/tests/test_vm.rs rename to synthesizer/src/vm/tests/test_vm_standard.rs index 0962b1093e..1724a06ddb 100644 --- a/synthesizer/src/vm/tests/test_vm.rs +++ b/synthesizer/src/vm/tests/test_vm_standard.rs @@ -20,11 +20,11 @@ fn test_multiple_deployments_and_multiple_executions() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Fetch the unspent records. let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); @@ -174,7 +174,7 @@ fn test_load_deployments_with_imports() { let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); + let vm = sample_vm(); // Initialize the genesis block. let genesis = vm.genesis_beacon(&caller_private_key, rng).unwrap(); // Update the VM. @@ -282,12 +282,12 @@ fn test_multiple_external_calls() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); let address = Address::try_from(&caller_private_key).unwrap(); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Fetch the unspent records. let records = genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); @@ -913,7 +913,7 @@ fn test_transfer_public_from_user() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); let caller_address = Address::try_from(&caller_private_key).unwrap(); // Initialize a recipient. @@ -921,10 +921,10 @@ fn test_transfer_public_from_user() { let recipient_address = Address::try_from(&recipient_private_key).unwrap(); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); + let vm = sample_vm(); // Update the VM. vm.add_next_block(&genesis).unwrap(); @@ -1004,7 +1004,7 @@ fn test_transfer_public_as_signer_from_user() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); let caller_address = Address::try_from(&caller_private_key).unwrap(); // Initialize a recipient. @@ -1012,10 +1012,10 @@ fn test_transfer_public_as_signer_from_user() { let recipient_address = Address::try_from(&recipient_private_key).unwrap(); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); + let vm = sample_vm(); // Update the VM. vm.add_next_block(&genesis).unwrap(); @@ -1095,7 +1095,7 @@ fn transfer_public_from_program() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); let caller_address = Address::try_from(&caller_private_key).unwrap(); // Initialize a recipient. @@ -1103,10 +1103,10 @@ fn transfer_public_from_program() { let recipient_address = Address::try_from(&recipient_private_key).unwrap(); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); + let vm = sample_vm(); // Update the VM. vm.add_next_block(&genesis).unwrap(); @@ -1286,7 +1286,7 @@ fn transfer_public_as_signer_from_program() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); let caller_address = Address::try_from(&caller_private_key).unwrap(); // Initialize a recipient. @@ -1294,10 +1294,10 @@ fn transfer_public_as_signer_from_program() { let recipient_address = Address::try_from(&recipient_private_key).unwrap(); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); + let vm = sample_vm(); // Update the VM. vm.add_next_block(&genesis).unwrap(); @@ -1421,7 +1421,7 @@ fn test_transfer_public_to_private_from_program() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); let caller_address = Address::try_from(&caller_private_key).unwrap(); // Initialize a recipient. @@ -1429,10 +1429,10 @@ fn test_transfer_public_to_private_from_program() { let recipient_address = Address::try_from(&recipient_private_key).unwrap(); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); + let vm = sample_vm(); // Update the VM. vm.add_next_block(&genesis).unwrap(); @@ -1604,13 +1604,13 @@ fn test_large_transaction_is_aborted() { let rng = &mut TestRng::default(); // Initialize a new caller. - let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let caller_private_key = sample_genesis_private_key(rng); // Initialize the genesis block. - let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + let genesis = sample_genesis_block(rng); // Initialize the VM. - let vm = crate::vm::test_helpers::sample_vm(); + let vm = sample_vm(); // Update the VM. vm.add_next_block(&genesis).unwrap(); diff --git a/synthesizer/src/vm/tests/test_vm_update.rs b/synthesizer/src/vm/tests/test_vm_update.rs new file mode 100644 index 0000000000..f2aceaaad4 --- /dev/null +++ b/synthesizer/src/vm/tests/test_vm_update.rs @@ -0,0 +1,142 @@ +// Copyright 2024 Aleo Network Foundation +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use synthesizer_program::StackProgram; + +#[test] +fn test_simple_update() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + vm.add_next_block(&genesis)?; + + // Initialize the program. + let program = Program::from_str( + r" +program adder.aleo; + +function binary_add: + input r0 as u8.public; + input r1 as u8.public; + add r0 r1 into r2; + output r2 as u8.public; + ", + )?; + + // Deploy the program. + let transaction = vm.deploy_updatable( + &caller_private_key, + Address::try_from(&caller_private_key)?, + 0, + &program, + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + vm.add_next_block(&block)?; + + // Check that the program is deployed. + let stack = vm.process().read().get_stack("adder.aleo")?; + assert_eq!(stack.program_id(), &ProgramID::from_str("adder.aleo")?); + assert_eq!(stack.edition(), 0); + + // Execute the program. + let original_execution = vm.execute( + &caller_private_key, + ("adder.aleo", "binary_add"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + vm.check_transaction(&original_execution, None, rng)?; + + // Check that the output is correct. + let output = match original_execution.transitions().next().unwrap().outputs().last().unwrap() { + Output::Public(_, Some(Plaintext::Literal(Literal::U8(value), _))) => **value, + output => bail!(format!("Unexpected output: {output}")), + }; + assert_eq!(output, 2u8); + + // Update the program. + let updated_program = Program::from_str( + r" +program adder.aleo; + +function binary_add: + input r0 as u8.public; + input r1 as u8.public; + add r0 r1 into r2; + add r2 1u8 into r3; + output r3 as u8.public; + ", + )?; + + // Deploy the updated program. + let transaction = vm.deploy_updatable( + &caller_private_key, + Address::try_from(&caller_private_key)?, + 1, + &updated_program, + None, + 0, + None, + rng, + )?; + assert_eq!(transaction.deployment().unwrap().edition(), 1); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Check that the program is updated. + let stack = vm.process().read().get_stack("adder.aleo")?; + assert_eq!(stack.program_id(), &ProgramID::from_str("adder.aleo")?); + assert_eq!(stack.edition(), 1); + + // Check that the old execution is no longer valid. + vm.check_transaction(&original_execution, None, rng)?; + + // Execute the updated program. + let new_execution = vm.execute( + &caller_private_key, + ("adder.aleo", "binary_add"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + vm.check_transaction(&new_execution, None, rng)?; + + // Check that the output is correct. + let output = match new_execution.transitions().next().unwrap().outputs().last().unwrap() { + Output::Public(_, Some(Plaintext::Literal(Literal::U8(value), _))) => **value, + output => bail!(format!("Unexpected output: {output}")), + }; + assert_eq!(output, 3u8); + + Ok(()) +} From 432b4a3f9e0065b45b4865a16314772bef220f26 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:41:12 -0800 Subject: [PATCH 30/46] Add some VM tests --- .../block/src/transactions/confirmed/mod.rs | 6 +- synthesizer/src/vm/tests/test_vm_update.rs | 521 ++++++++++++++++++ 2 files changed, 524 insertions(+), 3 deletions(-) diff --git a/ledger/block/src/transactions/confirmed/mod.rs b/ledger/block/src/transactions/confirmed/mod.rs index 9593e2ace5..9595b757e4 100644 --- a/ledger/block/src/transactions/confirmed/mod.rs +++ b/ledger/block/src/transactions/confirmed/mod.rs @@ -71,10 +71,10 @@ impl ConfirmedTransaction { finalize_operations.len() ); } - // Ensure the number of program mappings matches the number of 'InitializeMapping' finalize operations. - if num_initialize_mappings != program.mappings().len() { + // Ensure the number of program mappings bounds the number of 'InitializeMapping' finalize operations. + if num_initialize_mappings > program.mappings().len() { bail!( - "Transaction '{}' (deploy) must contain '{}' 'InitializeMapping' operations (found '{num_initialize_mappings}')", + "Transaction '{}' (deploy) must contain at most '{}' 'InitializeMapping' operations (found '{num_initialize_mappings}')", transaction.id(), program.mappings().len(), ) diff --git a/synthesizer/src/vm/tests/test_vm_update.rs b/synthesizer/src/vm/tests/test_vm_update.rs index f2aceaaad4..80d02761cf 100644 --- a/synthesizer/src/vm/tests/test_vm_update.rs +++ b/synthesizer/src/vm/tests/test_vm_update.rs @@ -16,6 +16,10 @@ use super::*; use synthesizer_program::StackProgram; +// This test checks that: +// - the logic of a simple transition without records can be updated. +// - once a program is updated, the old transitions are no longer valid. +// - an invalid authority cannot update a program. #[test] fn test_simple_update() -> Result<()> { let rng = &mut TestRng::default(); @@ -95,6 +99,22 @@ function binary_add: ", )?; + // Attempt to deploy the updated program with an invalid authority. + let invalid_private_key = PrivateKey::new(rng)?; + let transaction = vm.deploy_updatable( + &invalid_private_key, + Address::try_from(&caller_private_key)?, + 1, + &updated_program, + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &invalid_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + // Deploy the updated program. let transaction = vm.deploy_updatable( &caller_private_key, @@ -140,3 +160,504 @@ function binary_add: Ok(()) } + +// This test checks that a program deployed with `VM::deploy`, which uses `ProgramOwner::V1` cannot be updated. +#[test] +fn test_program_owner_v1_is_not_updatable() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key)?; + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + vm.add_next_block(&genesis)?; + + // Initialize the program. + let program = Program::from_str("program basic.aleo;function foo:")?; + + // Deploy the program. + let transaction = vm.deploy(&caller_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + vm.add_next_block(&block)?; + + // Initialize the updated program. + let updated_program = Program::from_str("program basic.aleo;function foo:function bar:")?; + + // Attempt to deploy the updated program using `VM::deploy`. + let transaction = vm.deploy(&caller_private_key, &updated_program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + + // Attempt to deploy the updated program using `VM::deploy_updatable`. + let transaction = + vm.deploy_updatable(&caller_private_key, caller_address, 1, &updated_program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + + Ok(()) +} + +// This test checks that: +// - the first instance of a program must be the zero-th edition. +// - subsequent updates to the program must be sequential. +#[test] +fn test_editions_are_sequential() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key)?; + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize two VMs. + let off_chain_vm = sample_vm(); + let on_chain_vm = sample_vm(); + off_chain_vm.add_next_block(&genesis)?; + on_chain_vm.add_next_block(&genesis)?; + + // Define the three versions of the program. + let program_v0 = Program::from_str("program basic.aleo;function foo:")?; + let program_v1 = Program::from_str("program basic.aleo;function foo:function bar:")?; + let program_v2 = Program::from_str("program basic.aleo;function foo:function bar:function baz:")?; + + // Using the off-chain VM, generate a sequence of deployments. + let deployment_v0_pass = + off_chain_vm.deploy_updatable(&caller_private_key, caller_address, 0, &program_v0, None, 0, None, rng)?; + off_chain_vm.process().write().add_program(&program_v0)?; + let deployment_v1_fail = + off_chain_vm.deploy_updatable(&caller_private_key, caller_address, 1, &program_v1, None, 0, None, rng)?; + let deployment_v1_pass = + off_chain_vm.deploy_updatable(&caller_private_key, caller_address, 1, &program_v1, None, 0, None, rng)?; + let deployment_v2_as_v1_fail = + off_chain_vm.deploy_updatable(&caller_private_key, caller_address, 1, &program_v2, None, 0, None, rng)?; + off_chain_vm.process().write().add_program(&program_v1)?; + let deployment_v2_fail = + off_chain_vm.deploy_updatable(&caller_private_key, caller_address, 2, &program_v2, None, 0, None, rng)?; + let deployment_v2_pass = + off_chain_vm.deploy_updatable(&caller_private_key, caller_address, 2, &program_v2, None, 0, None, rng)?; + + // Deploy the programs to the on-chain VM individually in the following sequence: + // - deployment_v1_fail + // - deployment_v0_pass + // - deployment_v2_fail + // - deployment_v1_pass + // - deployment_v2_as_v1_fail + // - deployment_v2_pass + // Their name should indicate whether the deployment should pass or fail. + + // This deployment should fail because the it is not the zero-th edition. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v1_fail], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 1); + on_chain_vm.add_next_block(&block)?; + + // This deployment should pass. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v0_pass], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + on_chain_vm.add_next_block(&block)?; + let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; + assert_eq!(stack.edition(), 0); + + // This deployment should fail because it does not increment the edition. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v2_fail], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 1); + on_chain_vm.add_next_block(&block)?; + + // This deployment should pass. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v1_pass], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + on_chain_vm.add_next_block(&block)?; + let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; + assert_eq!(stack.edition(), 1); + + // This deployment should fail because it attempt to redeploy at the same edition. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v2_as_v1_fail], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 1); + on_chain_vm.add_next_block(&block)?; + + // This deployment should pass. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v2_pass], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + on_chain_vm.add_next_block(&block)?; + let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; + assert_eq!(stack.edition(), 2); + + Ok(()) +} + +// This test checks that: +// - records created before an update are still valid after an update. +// - records created after an update can be created and used in the updated program. +// - records are semantically distinct (old records cannot be used in functions that require new records). +// - functions can be disabled using `assert.neq self.caller self.caller`. +#[test] +fn test_update_with_records() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_view_key = ViewKey::try_from(&caller_private_key)?; + let caller_address = Address::try_from(&caller_private_key)?; + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + vm.add_next_block(&genesis)?; + + // Define the two versions of the program. + let program_v0 = Program::from_str( + r" +program record_test.aleo; + +record data_v1: + owner as address.private; + data as u8.public; + +function mint: + input r0 as u8.public; + cast self.caller r0 into r1 as data_v1.record; + output r1 as data_v1.record;", + )?; + + let program_v1 = Program::from_str( + r" +program record_test.aleo; + +record data_v1: + owner as address.private; + data as u8.public; + +record data_v2: + owner as address.private; + data as u8.public; + +function mint: + input r0 as u8.public; + assert.neq self.caller self.caller; + cast self.caller r0 into r1 as data_v1.record; + output r1 as data_v1.record; + +function convert: + input r0 as data_v1.record; + cast r0.owner r0.data into r1 as data_v2.record; + output r1 as data_v2.record; + +function burn: + input r0 as data_v2.record;", + )?; + + // Deploy the first version of the program. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 0, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Execute the mint function twice. + let mint_execution_0 = vm.execute( + &caller_private_key, + ("record_test.aleo", "mint"), + vec![Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let mint_execution_1 = vm.execute( + &caller_private_key, + ("record_test.aleo", "mint"), + vec![Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[mint_execution_0, mint_execution_1], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + let mut v1_records = block + .records() + .into_iter() + .map(|(_, record)| record.decrypt(&caller_view_key)) + .collect::>>>>()?; + assert_eq!(v1_records.len(), 2); + vm.add_next_block(&block)?; + + // Update the program. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 1, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Attempt to execute the mint function. + assert!( + vm.execute( + &caller_private_key, + ("record_test.aleo", "mint"), + vec![Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng + ) + .is_err() + ); + + // Get the first record and execute the convert function. + let record = v1_records.pop().unwrap(); + let convert_execution = vm.execute( + &caller_private_key, + ("record_test.aleo", "convert"), + vec![Value::Record(record)].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[convert_execution], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + let mut v2_records = block + .records() + .into_iter() + .map(|(_, record)| record.decrypt(&caller_view_key)) + .collect::>>>>()?; + assert_eq!(v2_records.len(), 1); + vm.add_next_block(&block)?; + + // Get the v2 record and execute the burn function. + let record = v2_records.pop().unwrap(); + let burn_execution = vm.execute( + &caller_private_key, + ("record_test.aleo", "burn"), + vec![Value::Record(record)].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[burn_execution], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Attempt to execute the burn function with the remaining v1 record. + let record = v1_records.pop().unwrap(); + assert!( + vm.execute( + &caller_private_key, + ("record_test.aleo", "burn"), + vec![Value::Record(record)].into_iter(), + None, + 0, + None, + rng + ) + .is_err() + ); + + Ok(()) +} + +// This test checks that: +// - mappings created before an update are still valid after an update. +// - mappings created by and updated are correctly initialized and usable in the program. +// - functions can be disabled by inserting a failing condition in the on-chain logic. +#[test] +fn test_update_with_mappings() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key)?; + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + vm.add_next_block(&genesis)?; + + // Define the two versions of the program. + let program_v0 = Program::from_str( + r" +program mapping_test.aleo; + +mapping data_v1: + key as u8.public; + value as u8.public; + +function store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + async store_data_v1 r0 r1 into r2; + output r2 as mapping_test.aleo/store_data_v1.future; +finalize store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + set r1 into data_v1[r0];", + )?; + + let program_v1 = Program::from_str( + r" +program mapping_test.aleo; + +mapping data_v1: + key as u8.public; + value as u8.public; + +mapping data_v2: + key as u8.public; + value as u8.public; + +function store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + async store_data_v1 r0 r1 into r2; + output r2 as mapping_test.aleo/store_data_v1.future; +finalize store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + assert.neq true true; + +function migrate_data_v1_to_v2: + input r0 as u8.public; + async migrate_data_v1_to_v2 r0 into r1; + output r1 as mapping_test.aleo/migrate_data_v1_to_v2.future; +finalize migrate_data_v1_to_v2: + input r0 as u8.public; + get data_v1[r0] into r1; + remove data_v1[r0]; + set r1 into data_v2[r0]; + +function store_data_v2: + input r0 as u8.public; + input r1 as u8.public; + async store_data_v2 r0 r1 into r2; + output r2 as mapping_test.aleo/store_data_v2.future; +finalize store_data_v2: + input r0 as u8.public; + input r1 as u8.public; + set r1 into data_v2[r0];", + )?; + + // Deploy the first version of the program. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 0, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Execute the store_data_v1 function. + let store_data_v1_execution = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "store_data_v1"), + vec![Value::from_str("0u8")?, Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[store_data_v1_execution], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Check that the value was stored correctly. + let value = match vm.finalize_store().get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v1")?, + &Plaintext::from_str("0u8")?, + )? { + Some(Value::Plaintext(Plaintext::Literal(Literal::U8(value), _))) => *value, + value => bail!(format!("Unexpected value: {:?}", value)), + }; + assert_eq!(value, 0u8); + + // Update the program. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 1, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Attempt to execute the store_data_v1 function. + let transaction = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "store_data_v1"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_rejected(), 1); + vm.add_next_block(&block)?; + + // Execute the migrate_data_v1_to_v2 function. + let migrate_data_v1_to_v2_execution = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "migrate_data_v1_to_v2"), + vec![Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[migrate_data_v1_to_v2_execution], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Check that the value was migrated correctly. + let value = match vm.finalize_store().get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v2")?, + &Plaintext::from_str("0u8")?, + )? { + Some(Value::Plaintext(Plaintext::Literal(Literal::U8(value), _))) => *value, + value => bail!(format!("Unexpected value: {:?}", value)), + }; + assert_eq!(value, 0u8); + + // Check that the old value was removed. + assert!( + vm.finalize_store() + .get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v1")?, + &Plaintext::from_str("0u8")? + )? + .is_none() + ); + + // Execute the store_data_v2 function. + let store_data_v2_execution = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "store_data_v2"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[store_data_v2_execution], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Check that the value was stored correctly. + let value = match vm.finalize_store().get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v2")?, + &Plaintext::from_str("1u8")?, + )? { + Some(Value::Plaintext(Plaintext::Literal(Literal::U8(value), _))) => *value, + value => bail!(format!("Unexpected value: {:?}", value)), + }; + assert_eq!(value, 1u8); + + Ok(()) +} From c4c6a6947c097464da35197b65a8932f04442d32 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:42:16 -0800 Subject: [PATCH 31/46] Clippy --- synthesizer/src/vm/tests/test_vm_update.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/synthesizer/src/vm/tests/test_vm_update.rs b/synthesizer/src/vm/tests/test_vm_update.rs index 80d02761cf..c5e382f6f9 100644 --- a/synthesizer/src/vm/tests/test_vm_update.rs +++ b/synthesizer/src/vm/tests/test_vm_update.rs @@ -385,7 +385,6 @@ function burn: assert_eq!(block.aborted_transaction_ids().len(), 0); let mut v1_records = block .records() - .into_iter() .map(|(_, record)| record.decrypt(&caller_view_key)) .collect::>>>>()?; assert_eq!(v1_records.len(), 2); @@ -426,7 +425,6 @@ function burn: assert_eq!(block.aborted_transaction_ids().len(), 0); let mut v2_records = block .records() - .into_iter() .map(|(_, record)| record.decrypt(&caller_view_key)) .collect::>>>>()?; assert_eq!(v2_records.len(), 1); From 06b4a90d07135cf360f55f51420887faa16bfc17 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 11 Feb 2025 19:07:52 -0800 Subject: [PATCH 32/46] Test effect of updates on dependents --- .../src/stack/finalize_types/initialize.rs | 2 +- synthesizer/src/vm/tests/test_vm_update.rs | 318 ++++++++++++++++-- 2 files changed, 297 insertions(+), 23 deletions(-) diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index b8375783d4..7f69385c2b 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -476,7 +476,7 @@ impl FinalizeTypes { let program_id = locator.program_id(); // Ensure the locator does not reference the current program. if stack.program_id() == program_id { - bail!("Locator '{locator}' does not reference an external mapping."); + bail!("Locator '{locator}' does not reference an external program."); } // Ensure the current program contains an import for this external program. if !stack.program().imports().keys().contains(program_id) { diff --git a/synthesizer/src/vm/tests/test_vm_update.rs b/synthesizer/src/vm/tests/test_vm_update.rs index c5e382f6f9..23118b2fab 100644 --- a/synthesizer/src/vm/tests/test_vm_update.rs +++ b/synthesizer/src/vm/tests/test_vm_update.rs @@ -14,11 +14,12 @@ // limitations under the License. use super::*; +use std::panic::AssertUnwindSafe; use synthesizer_program::StackProgram; // This test checks that: // - the logic of a simple transition without records can be updated. -// - once a program is updated, the old transitions are no longer valid. +// - once a program is updated, the old executions are no longer valid. // - an invalid authority cannot update a program. #[test] fn test_simple_update() -> Result<()> { @@ -76,7 +77,7 @@ function binary_add: None, rng, )?; - vm.check_transaction(&original_execution, None, rng)?; + assert!(vm.check_transaction(&original_execution, None, rng).is_ok()); // Check that the output is correct. let output = match original_execution.transitions().next().unwrap().outputs().last().unwrap() { @@ -93,9 +94,8 @@ program adder.aleo; function binary_add: input r0 as u8.public; input r1 as u8.public; - add r0 r1 into r2; - add r2 1u8 into r3; - output r3 as u8.public; + add.w r0 r1 into r2; + output r2 as u8.public; ", )?; @@ -128,7 +128,7 @@ function binary_add: )?; assert_eq!(transaction.deployment().unwrap().edition(), 1); let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Check that the program is updated. @@ -137,7 +137,8 @@ function binary_add: assert_eq!(stack.edition(), 1); // Check that the old execution is no longer valid. - vm.check_transaction(&original_execution, None, rng)?; + vm.partially_verified_transactions().write().clear(); + assert!(vm.check_transaction(&original_execution, None, rng).is_err()); // Execute the updated program. let new_execution = vm.execute( @@ -149,14 +150,14 @@ function binary_add: None, rng, )?; - vm.check_transaction(&new_execution, None, rng)?; + assert!(vm.check_transaction(&new_execution, None, rng).is_ok()); // Check that the output is correct. let output = match new_execution.transitions().next().unwrap().outputs().last().unwrap() { Output::Public(_, Some(Plaintext::Literal(Literal::U8(value), _))) => **value, output => bail!(format!("Unexpected output: {output}")), }; - assert_eq!(output, 3u8); + assert_eq!(output, 2u8); Ok(()) } @@ -261,7 +262,7 @@ fn test_editions_are_sequential() -> Result<()> { // This deployment should pass. let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v0_pass], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); on_chain_vm.add_next_block(&block)?; let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; assert_eq!(stack.edition(), 0); @@ -273,7 +274,7 @@ fn test_editions_are_sequential() -> Result<()> { // This deployment should pass. let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v1_pass], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); on_chain_vm.add_next_block(&block)?; let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; assert_eq!(stack.edition(), 1); @@ -285,7 +286,7 @@ fn test_editions_are_sequential() -> Result<()> { // This deployment should pass. let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v2_pass], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); on_chain_vm.add_next_block(&block)?; let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; assert_eq!(stack.edition(), 2); @@ -359,7 +360,7 @@ function burn: // Deploy the first version of the program. let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 0, &program_v0, None, 0, None, rng)?; let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Execute the mint function twice. @@ -382,7 +383,7 @@ function burn: rng, )?; let block = sample_next_block(&vm, &caller_private_key, &[mint_execution_0, mint_execution_1], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 2); let mut v1_records = block .records() .map(|(_, record)| record.decrypt(&caller_view_key)) @@ -393,7 +394,7 @@ function burn: // Update the program. let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 1, &program_v1, None, 0, None, rng)?; let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Attempt to execute the mint function. @@ -422,7 +423,7 @@ function burn: rng, )?; let block = sample_next_block(&vm, &caller_private_key, &[convert_execution], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); let mut v2_records = block .records() .map(|(_, record)| record.decrypt(&caller_view_key)) @@ -442,7 +443,7 @@ function burn: rng, )?; let block = sample_next_block(&vm, &caller_private_key, &[burn_execution], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Attempt to execute the burn function with the remaining v1 record. @@ -548,7 +549,7 @@ finalize store_data_v2: // Deploy the first version of the program. let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 0, &program_v0, None, 0, None, rng)?; let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Execute the store_data_v1 function. @@ -562,7 +563,7 @@ finalize store_data_v2: rng, )?; let block = sample_next_block(&vm, &caller_private_key, &[store_data_v1_execution], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Check that the value was stored correctly. @@ -579,7 +580,7 @@ finalize store_data_v2: // Update the program. let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 1, &program_v1, None, 0, None, rng)?; let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Attempt to execute the store_data_v1 function. @@ -607,7 +608,7 @@ finalize store_data_v2: rng, )?; let block = sample_next_block(&vm, &caller_private_key, &[migrate_data_v1_to_v2_execution], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Check that the value was migrated correctly. @@ -643,7 +644,7 @@ finalize store_data_v2: rng, )?; let block = sample_next_block(&vm, &caller_private_key, &[store_data_v2_execution], rng)?; - assert_eq!(block.aborted_transaction_ids().len(), 0); + assert_eq!(block.transactions().num_accepted(), 1); vm.add_next_block(&block)?; // Check that the value was stored correctly. @@ -659,3 +660,276 @@ finalize store_data_v2: Ok(()) } + +// This test checks that: +// - a dependent program accepts an update to off-chain logic +// - a dependent program accepts an update to on-chain logic +// - a dependent program can fix a specific version of the dependency +// - old executions of the dependent program are no longer valid after an update +#[test] +fn test_update_with_dependents() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key)?; + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + vm.add_next_block(&genesis)?; + + // Define the two versions of the dependency program. + let dependency_v0 = Program::from_str( + r" +program dependency.aleo; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + add r0 r1 into r2; + output r0 as u8.public; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + add r0 r1 into r2; + async sum_and_check into r3; + output r2 as u8.public; + output r3 as dependency.aleo/sum_and_check.future; +finalize sum_and_check: + assert.eq true true;", + )?; + + let dependency_v1 = Program::from_str( + r" +program dependency.aleo; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + add.w r0 r1 into r2; + output r0 as u8.public; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + add.w r0 r1 into r2; + async sum_and_check into r3; + output r2 as u8.public; + output r3 as dependency.aleo/sum_and_check.future; +finalize sum_and_check: + assert.eq true false;", + )?; + + // Define the two versions of the dependent program. + let dependent_v0 = Program::from_str( + r" +import dependency.aleo; + +program dependent.aleo; + +function sum_unchecked: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + output r2 as u8.public; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + async sum into r3; + output r2 as u8.public; + output r3 as dependent.aleo/sum.future; +finalize sum: + global.get dependency.aleo/edition into r0; + assert.eq r0 0u16; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum_and_check r0 r1 into r2 r3; + async sum_and_check r3 into r4; + output r2 as u8.public; + output r4 as dependent.aleo/sum_and_check.future; +finalize sum_and_check: + input r0 as dependency.aleo/sum_and_check.future; + await r0;", + )?; + + let dependent_v1 = Program::from_str( + r" +import dependency.aleo; + +program dependent.aleo; + +function sum_unchecked: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + output r2 as u8.public; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + async sum into r3; + output r2 as u8.public; + output r3 as dependent.aleo/sum.future; +finalize sum: + global.get dependency.aleo/edition into r0; + assert.eq r0 1u16; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum_and_check r0 r1 into r2 r3; + async sum_and_check r3 into r4; + output r2 as u8.public; + output r4 as dependent.aleo/sum_and_check.future; +finalize sum_and_check: + input r0 as dependency.aleo/sum_and_check.future; + await r0;", + )?; + + // At a high level, this test will: + // 1. Deploy the v0 dependency and v0 dependent. + // 2. Verify that the the dependent program can be correctly executed. + // 3. Update the dependency to v1. + // 4. Verify that the call to `sum_and_check` automatically uses the new logic, however, the call `sum` fails because the edition is not 0. + // 5. Update the dependent to v1. + // 6. Verify that the call to `sum` now passes because the edition is 1. + + // Deploy the v0 dependency. + let transaction = + vm.deploy_updatable(&caller_private_key, caller_address, 0, &dependency_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + // Deploy the v0 dependent. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 0, &dependent_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + // Execute the functions. + let tx_1 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum_and_check"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + vm.add_next_block(&block)?; + + // Verify that the sum function fails on overflow. + let result = std::panic::catch_unwind(AssertUnwindSafe(|| { + vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("255u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + ) + })); + assert!(result.is_err()); + + // Get a valid execution before the dependency update. + let sum_unchecked = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum_unchecked"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + assert!(vm.check_transaction(&sum_unchecked, None, rng).is_ok()); + + // Update the dependency to v1. + let transaction = + vm.deploy_updatable(&caller_private_key, caller_address, 1, &dependency_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + // Verify that the original sum transaction fails after the dependency update. + vm.partially_verified_transactions().write().clear(); + assert!(vm.check_transaction(&sum_unchecked, None, rng).is_err()); + let block = sample_next_block(&vm, &caller_private_key, &[sum_unchecked], rng)?; + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + + // Verify that the sum function fails on edition check. + let tx_1 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum_and_check"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_rejected(), 2); + vm.add_next_block(&block)?; + + // Update the dependent to v1. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 1, &dependent_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + // Verify that the sum function passes. + let tx_1 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("255u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + vm.add_next_block(&block)?; + + Ok(()) +} From 45caf0142b70a752825ca091fd6923bc725aead9 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 12 Feb 2025 07:34:30 -0800 Subject: [PATCH 33/46] Add test with cycle, currently mutual recursion fails to deploy, however PR #2598 may allow that to happen --- synthesizer/src/vm/tests/test_vm_update.rs | 157 +++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/synthesizer/src/vm/tests/test_vm_update.rs b/synthesizer/src/vm/tests/test_vm_update.rs index 23118b2fab..8d122991bd 100644 --- a/synthesizer/src/vm/tests/test_vm_update.rs +++ b/synthesizer/src/vm/tests/test_vm_update.rs @@ -933,3 +933,160 @@ finalize sum_and_check: Ok(()) } + +// This test checks that: +// - programs can be updated to create cycles in the dependency graph. +// - programs can be updated to create cycles in the call graph. +// - executions of cyclic programs w.r.t. to the call graph are rejected. +#[test] +fn test_update_with_cycles() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key)?; + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + vm.add_next_block(&genesis)?; + + // Define the programs. + let first_v0 = Program::from_str( + r" +program first.aleo; + +function foo: + input r0 as u8.public; + output r0 as u8.public; + ", + )?; + + let second_v0 = Program::from_str( + r" +import first.aleo; + +program second.aleo; + +function foo: + input r0 as u8.public; + call first.aleo/foo r0 into r1; + output r1 as u8.public; + ", + )?; + + let first_v1 = Program::from_str( + r" +import second.aleo; + +program first.aleo; + +function foo: + input r0 as u8.public; + output r0 as u8.public; + ", + )?; + + let first_v2 = Program::from_str( + r" +import second.aleo; + +program first.aleo; + +function foo: + input r0 as u8.public; + call second.aleo/foo r0 into r1; + output r1 as u8.public; + ", + )?; + + // Deploy the first version of the programs. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 0, &first_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 0, &second_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + // Verify that both can be executed correctly. + let tx_1 = vm.execute( + &caller_private_key, + ("first.aleo", "foo"), + vec![Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("second.aleo", "foo"), + vec![Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + vm.add_next_block(&block)?; + + // Update the first program to create a cycle in the dependency graph. + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 1, &first_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + // Verify that both programs can be executed correctly. + let tx_1 = vm.execute( + &caller_private_key, + ("first.aleo", "foo"), + vec![Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("second.aleo", "foo"), + vec![Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + vm.add_next_block(&block)?; + + // Update the first program to create mutual recursion. + println!("Attempting to deploy first_v2"); + let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 2, &first_v2, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block)?; + + // Verify that the first program is no longer executable. + assert!( + vm.execute( + &caller_private_key, + ("first.aleo", "foo"), + vec![Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + ) + .is_err() + ); + + // TODO: Mutate the transition to work correctly and attempt to execute. + + Ok(()) +} From ec32aad5a445d8cff997f20c7d283c7abd71ba3d Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:36:05 -0800 Subject: [PATCH 34/46] Add some protections against infinite recursion --- .../process/src/stack/authorization/mod.rs | 10 +++++++++- synthesizer/process/src/stack/call/mod.rs | 2 +- synthesizer/process/src/stack/mod.rs | 17 +++++++++++++---- synthesizer/src/vm/tests/test_vm_update.rs | 3 --- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/synthesizer/process/src/stack/authorization/mod.rs b/synthesizer/process/src/stack/authorization/mod.rs index b7aa4b9669..e86c2cb74e 100644 --- a/synthesizer/process/src/stack/authorization/mod.rs +++ b/synthesizer/process/src/stack/authorization/mod.rs @@ -145,8 +145,16 @@ impl Authorization { } /// Appends the given `Request` to the authorization. - pub fn push(&self, request: Request) { + pub fn push(&self, request: Request) -> Result<()> { + // Check that the number of requests is less than the maximum. + ensure!( + self.len() < Transaction::::MAX_TRANSITIONS, + "The number of requests in the authorization cannot exceed '{}'.", + Transaction::::MAX_TRANSITIONS + ); + // Append the request to the authorization. self.requests.write().push_back(request); + Ok(()) } /// Returns the requests in the authorization. diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index b4d1f63a53..7873e23112 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -256,7 +256,7 @@ impl CallTrait for Call { call_stack.push(request.clone())?; // Add the request to the authorization. - authorization.push(request.clone()); + authorization.push(request.clone())?; // Execute the request. let response = substack.execute_function::(call_stack, console_caller, root_tvk, rng)?; diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 6424f4bc04..940963261a 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -64,7 +64,7 @@ use console::{ }, types::{Field, Group}, }; -use ledger_block::{Deployment, Transition}; +use ledger_block::{Deployment, Transaction, Transition}; use synthesizer_program::{CallOperator, Closure, Function, Instruction, Operand, Program, traits::*}; use synthesizer_snark::{Certificate, ProvingKey, UniversalSRS, VerifyingKey}; @@ -135,9 +135,18 @@ impl CallStack { CallStack::Authorize(requests, ..) | CallStack::Synthesize(requests, ..) | CallStack::CheckDeployment(requests, ..) - | CallStack::PackageRun(requests, ..) => requests.push(request), - CallStack::Evaluate(authorization) => authorization.push(request), - CallStack::Execute(authorization, ..) => authorization.push(request), + | CallStack::PackageRun(requests, ..) => { + // Check that the number of requests does not exceed the maximum. + ensure!( + requests.len() < Transaction::::MAX_TRANSITIONS, + "The number of requests in the authorization cannot exceed '{}'.", + Transaction::::MAX_TRANSITIONS + ); + // Push the request to the stack. + requests.push(request) + } + CallStack::Evaluate(authorization) => authorization.push(request)?, + CallStack::Execute(authorization, ..) => authorization.push(request)?, } Ok(()) } diff --git a/synthesizer/src/vm/tests/test_vm_update.rs b/synthesizer/src/vm/tests/test_vm_update.rs index 8d122991bd..457979bba2 100644 --- a/synthesizer/src/vm/tests/test_vm_update.rs +++ b/synthesizer/src/vm/tests/test_vm_update.rs @@ -1066,7 +1066,6 @@ function foo: vm.add_next_block(&block)?; // Update the first program to create mutual recursion. - println!("Attempting to deploy first_v2"); let transaction = vm.deploy_updatable(&caller_private_key, caller_address, 2, &first_v2, None, 0, None, rng)?; let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; assert_eq!(block.transactions().num_accepted(), 1); @@ -1086,7 +1085,5 @@ function foo: .is_err() ); - // TODO: Mutate the transition to work correctly and attempt to execute. - Ok(()) } From 2811d7e535e6a65d95e26f46848e96df0f67df7b Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:03:20 -0800 Subject: [PATCH 35/46] Defend against recursion in get_number_of_calls and costs --- synthesizer/process/src/cost.rs | 131 +++++++++++------- .../process/src/stack/helpers/initialize.rs | 21 +-- synthesizer/process/src/stack/mod.rs | 53 +++++-- 3 files changed, 126 insertions(+), 79 deletions(-) diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 0e500a2ce5..9a48bbba51 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -14,12 +14,13 @@ // limitations under the License. use crate::{Process, Stack, StackProgramTypes}; +use std::sync::Arc; use console::{ prelude::*, program::{FinalizeType, Identifier, LiteralType, PlaintextType}, }; -use ledger_block::{Deployment, Execution}; +use ledger_block::{Deployment, Execution, Transaction}; use synthesizer_program::{CastType, Command, Finalize, Instruction, Operand, StackProgram}; /// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, synthesis cost, namespace cost)). @@ -407,60 +408,96 @@ pub fn cost_per_command( /// Returns the minimum number of microcredits required to run the finalize. pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Identifier) -> Result { - // Retrieve the finalize logic. - let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else { - // Return a finalize cost of 0, if the function does not have a finalize scope. - return Ok(0); - }; - // Get the cost of finalizing all futures. - let mut future_cost = 0u64; - for input in finalize.inputs() { - if let FinalizeType::Future(future) = input.finalize_type() { - // Get the external stack for the future. - let stack = stack.get_external_stack(future.program_id())?; - // Accumulate the finalize cost of the future. - future_cost = future_cost - .checked_add(cost_in_microcredits_v2(&stack, future.resource())?) - .ok_or(anyhow!("Finalize cost overflowed"))?; + // Initialize the base cost. + let mut finalize_cost = 0u64; + // A helper enum to track stacks. + enum StackRef<'a, N: Network> { + // Self's stack. + Internal(&'a Stack), + // An external stack. + External(Arc>), + } + // Initialize a queue of finalize blocks to tally. + let mut finalizes = vec![(StackRef::Internal(stack), *function_name)]; + // Initialize a counter for the number of finalize blocks seen. + let mut num_finalizes = 1; + // Iterate over the finalize blocks. + while let Some((stack_ref, function_name)) = finalizes.pop() { + // Ensure that the number of finalize blocks does not exceed the maximum. + // Note that one transition is reserved for the fee. + ensure!(num_finalizes < Transaction::::MAX_TRANSITIONS, "The number of finalize blocks cannot "); + // Get the finalize logic. If the function does not have a finalize scope then no cost is incurred. + if let Some(finalize) = match &stack_ref { + StackRef::Internal(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), + StackRef::External(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), + } { + // Queue the futures to be tallied. + for input in finalize.inputs() { + if let FinalizeType::Future(future) = input.finalize_type() { + // Get the external stack for the future. + let stack = stack.get_external_stack(future.program_id())?; + // Increment the number of finalize blocks seen. + num_finalizes += 1; + // Queue the future. + finalizes.push((StackRef::External(stack), *future.resource())); + } + } + // Sum the cost of all commands in the current block. + for command in finalize.commands() { + finalize_cost = finalize_cost + .checked_add(cost_per_command(stack, finalize, command, ConsensusFeeVersion::V2)?) + .ok_or(anyhow!("Finalize cost overflowed"))?; + } } } - // Aggregate the cost of all commands in the program. - finalize - .commands() - .iter() - .map(|command| cost_per_command(stack, finalize, command, ConsensusFeeVersion::V2)) - .try_fold(future_cost, |acc, res| { - res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) - }) + Ok(finalize_cost) } /// Returns the minimum number of microcredits required to run the finalize (deprecated). pub fn cost_in_microcredits_v1(stack: &Stack, function_name: &Identifier) -> Result { - // Retrieve the finalize logic. - let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else { - // Return a finalize cost of 0, if the function does not have a finalize scope. - return Ok(0); - }; - // Get the cost of finalizing all futures. - let mut future_cost = 0u64; - for input in finalize.inputs() { - if let FinalizeType::Future(future) = input.finalize_type() { - // Get the external stack for the future. - let stack = stack.get_external_stack(future.program_id())?; - // Accumulate the finalize cost of the future. - future_cost = future_cost - .checked_add(cost_in_microcredits_v1(&stack, future.resource())?) - .ok_or(anyhow!("Finalize cost overflowed"))?; + // Initialize the base cost. + let mut finalize_cost = 0u64; + // A helper enum to track stacks. + enum StackRef<'a, N: Network> { + // Self's stack. + Internal(&'a Stack), + // An external stack. + External(Arc>), + } + // Initialize a queue of finalize blocks to tally. + let mut finalizes = vec![(StackRef::Internal(stack), *function_name)]; + // Initialize a counter for the number of finalize blocks seen. + let mut num_finalizes = 1; + // Iterate over the finalize blocks. + while let Some((stack_ref, function_name)) = finalizes.pop() { + // Ensure that the number of finalize blocks does not exceed the maximum. + // Note that one transition is reserved for the fee. + ensure!(num_finalizes < Transaction::::MAX_TRANSITIONS, "The number of finalize blocks cannot "); + // Get the finalize logic. If the function does not have a finalize scope then no cost is incurred. + if let Some(finalize) = match &stack_ref { + StackRef::Internal(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), + StackRef::External(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), + } { + // Queue the futures to be tallied. + for input in finalize.inputs() { + if let FinalizeType::Future(future) = input.finalize_type() { + // Get the external stack for the future. + let stack = stack.get_external_stack(future.program_id())?; + // Increment the number of finalize blocks seen. + num_finalizes += 1; + // Queue the future. + finalizes.push((StackRef::External(stack), *future.resource())); + } + } + // Sum the cost of all commands in the current block. + for command in finalize.commands() { + finalize_cost = finalize_cost + .checked_add(cost_per_command(stack, finalize, command, ConsensusFeeVersion::V1)?) + .ok_or(anyhow!("Finalize cost overflowed"))?; + } } } - // Aggregate the cost of all commands in the program. - finalize - .commands() - .iter() - .map(|command| cost_per_command(stack, finalize, command, ConsensusFeeVersion::V1)) - .try_fold(future_cost, |acc, res| { - res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) - }) + Ok(finalize_cost) } #[cfg(test)] diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 73a200e1e4..421e43a0e7 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -31,7 +31,6 @@ impl Stack { universal_srs: process.universal_srs().clone(), proving_keys: Default::default(), verifying_keys: Default::default(), - number_of_calls: Default::default(), program_address: program.id().to_address()?, edition, }; @@ -57,24 +56,8 @@ impl Stack { // Add the function to the stack. stack.insert_function(function)?; // Determine the number of calls for the function. - let mut num_calls = 1; - for instruction in function.instructions() { - if let Instruction::Call(call) = instruction { - // Determine if this is a function call. - if call.is_function_call(&stack)? { - // Increment by the number of calls. - num_calls += 1 - } - } - } - // Check that the number of calls does not exceed the maximum. - // Note that one transition is reserved for the fee. - ensure!( - num_calls < ledger_block::Transaction::::MAX_TRANSITIONS, - "Number of calls exceeds the maximum allowed number of transitions" - ); - // Add the number of calls to the stack. - stack.number_of_calls.insert(*function.name(), num_calls); + // This includes a safety check for the maximum number of calls. + stack.get_number_of_calls(function.name())?; } // Return the stack. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 940963261a..6580c14263 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -196,8 +196,6 @@ pub struct Stack { proving_keys: Arc, ProvingKey>>>, /// The mapping of function name to verifying key. verifying_keys: Arc, VerifyingKey>>>, - /// The mapping of function names to the number of function calls. - number_of_calls: IndexMap, usize>, /// The program address. program_address: Address, /// The program edition. @@ -295,18 +293,47 @@ impl StackProgram for Stack { fn get_number_of_calls(&self, function_name: &Identifier) -> Result { // Initialize the base number of calls. let mut num_calls = 1; - // Determine the number of calls for the function. - for instruction in self.program.get_function_ref(function_name)?.instructions() { - if let Instruction::Call(call) = instruction { - // Determine if this is a function call. - if call.is_function_call(self)? { - // Increment by the number of calls. - num_calls += match call.operator() { - CallOperator::Locator(locator) => { - self.get_external_stack(locator.program_id())?.get_number_of_calls(locator.resource())? + // A helper enum to track stacks. + enum StackRef<'a, N: Network> { + // Self's stack. + Internal(&'a Stack), + // An external stack. + External(Arc>), + } + // Initialize a queue of functions to check. + let mut queue = vec![(StackRef::Internal(self), *function_name)]; + // Iterate over the queue. + while let Some((stack_ref, function_name)) = queue.pop() { + // Ensure that the number of calls does not exceed the maximum. + // Note that one transition is reserved for the fee. + ensure!( + num_calls < Transaction::::MAX_TRANSITIONS, + "Number of calls exceeds the maximum allowed number of transitions" + ); + + // Determine the number of calls for the function. + for instruction in match &stack_ref { + StackRef::Internal(stack) => stack.get_function_ref(&function_name)?.instructions(), + StackRef::External(stack) => stack.get_function_ref(&function_name)?.instructions(), + } { + if let Instruction::Call(call) = instruction { + // Determine if this is a function call. + if call.is_function_call(self)? { + // Increment by the number of calls. + num_calls += 1; + // Add the function to the queue. + match call.operator() { + CallOperator::Locator(locator) => { + queue.push(( + StackRef::External(self.get_external_stack(locator.program_id())?), + *locator.resource(), + )); + } + CallOperator::Resource(resource) => { + queue.push((StackRef::Internal(self), *resource)); + } } - CallOperator::Resource(resource) => self.get_number_of_calls(resource)?, - }; + } } } } From 13e369290c7a775e511e6739bb6d51602442acd8 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:18:04 -0800 Subject: [PATCH 36/46] Rename global.get to metadata.get --- synthesizer/process/src/cost.rs | 4 +- .../src/stack/finalize_types/initialize.rs | 14 ++--- .../{global_get.rs => metadata_get.rs} | 51 +++++++++---------- synthesizer/program/src/logic/command/mod.rs | 36 ++++++------- synthesizer/src/vm/tests/test_vm_update.rs | 4 +- .../vm/execute_and_finalize/global_get.aleo | 4 +- .../invalid_global_fail.aleo | 2 +- 7 files changed, 56 insertions(+), 59 deletions(-) rename synthesizer/program/src/logic/command/{global_get.rs => metadata_get.rs} (78%) diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 9a48bbba51..71cf773375 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -394,8 +394,8 @@ pub fn cost_per_command( Command::GetOrUse(command) => { cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } - // TODO: This should be updated once general `global.get` is implemented. - Command::GlobalGet(_) => Ok(500), + // TODO: This should be updated once general `metadata.get` is implemented. + Command::MetadataGet(_) => Ok(500), Command::RandChaCha(_) => Ok(25_000), Command::Remove(_) => Ok(SET_BASE_COST), Command::Set(command) => { diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index 7f69385c2b..f0650153fe 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -14,7 +14,7 @@ // limitations under the License. use super::*; -use synthesizer_program::GlobalGet; +use synthesizer_program::MetadataGet; impl FinalizeTypes { /// Initializes a new instance of `FinalizeTypes` for the given finalize. @@ -179,7 +179,7 @@ impl FinalizeTypes { Command::Contains(contains) => self.check_contains(stack, contains)?, Command::Get(get) => self.check_get(stack, get)?, Command::GetOrUse(get_or_use) => self.check_get_or_use(stack, get_or_use)?, - Command::GlobalGet(global_get) => self.check_global_get(stack, global_get)?, + Command::MetadataGet(metadata_get) => self.check_metadata_get(stack, metadata_get)?, Command::RandChaCha(rand_chacha) => self.check_rand_chacha(stack, finalize.name(), rand_chacha)?, Command::Remove(remove) => self.check_remove(stack, finalize.name(), remove)?, Command::Set(set) => self.check_set(stack, finalize.name(), set)?, @@ -462,15 +462,15 @@ impl FinalizeTypes { Ok(()) } - /// Ensures the given `global.get` command is well-formed. + /// Ensures the given `metadata.get` command is well-formed. #[inline] - fn check_global_get( + fn check_metadata_get( &mut self, stack: &(impl StackMatches + StackProgram), - global_get: &GlobalGet, + metadata_get: &MetadataGet, ) -> Result<()> { // Ensure that the global name is `edition`. - let global_name = match global_get.global() { + let global_name = match metadata_get.global() { CallOperator::Locator(locator) => { // Retrieve the program ID. let program_id = locator.program_id(); @@ -489,7 +489,7 @@ impl FinalizeTypes { ensure!(global_name.to_string() == "edition", "Invalid global name: {global_name}"); // Get the destination register. - let destination = global_get.destination().clone(); + let destination = metadata_get.destination().clone(); // Ensure the destination register is a locator (and does not reference an access). ensure!(matches!(destination, Register::Locator(..)), "Destination '{destination}' must be a locator."); // Insert the destination register. diff --git a/synthesizer/program/src/logic/command/global_get.rs b/synthesizer/program/src/logic/command/metadata_get.rs similarity index 78% rename from synthesizer/program/src/logic/command/global_get.rs rename to synthesizer/program/src/logic/command/metadata_get.rs index 562fb9aab6..303e0464d8 100644 --- a/synthesizer/program/src/logic/command/global_get.rs +++ b/synthesizer/program/src/logic/command/metadata_get.rs @@ -24,17 +24,17 @@ use console::{ types::U16, }; -/// A global get command, e.g. `global.get owner into r1;`. +/// A command to get metadata about a program, e.g. `metadata.get owner into r1;`. /// Gets the value stored at `global` and stores the result in `destination`. #[derive(Clone)] -pub struct GlobalGet { +pub struct MetadataGet { /// The global ID. global: CallOperator, /// The destination register. destination: Register, } -impl PartialEq for GlobalGet { +impl PartialEq for MetadataGet { /// Returns true if the two objects are equal. #[inline] fn eq(&self, other: &Self) -> bool { @@ -42,9 +42,9 @@ impl PartialEq for GlobalGet { } } -impl Eq for GlobalGet {} +impl Eq for MetadataGet {} -impl std::hash::Hash for GlobalGet { +impl std::hash::Hash for MetadataGet { /// Returns the hash of the object. #[inline] fn hash(&self, state: &mut H) { @@ -53,11 +53,11 @@ impl std::hash::Hash for GlobalGet { } } -impl GlobalGet { +impl MetadataGet { /// Returns the opcode. #[inline] pub const fn opcode() -> Opcode { - Opcode::Command("global.get") + Opcode::Command("metadata.get") } /// Returns the global ID. @@ -73,11 +73,8 @@ impl GlobalGet { } } -impl GlobalGet { +impl MetadataGet { /// Finalizes the command. - // Note that this implementation is specifically tailored for `global.get edition into r;`. - // This was done to minimize the number of changes introduced to the code base. - // When global values are introduced in the `FinalizeStoreTrait`, this implementation should be refactored. #[inline] pub fn finalize( &self, @@ -94,7 +91,7 @@ impl GlobalGet { }; // Ensure that the global name is `edition`. - // This is presently the only valid use of `global.get`. + // This is presently the only valid use of `metadata.get`. ensure!(global_name.to_string() == "edition", "Invalid global name: {global_name}"); // Lookup the edition in the appropriate stack. @@ -110,7 +107,7 @@ impl GlobalGet { } } -impl Parser for GlobalGet { +impl Parser for MetadataGet { /// Parses a string into the command. #[inline] fn parse(string: &str) -> ParserResult { @@ -142,7 +139,7 @@ impl Parser for GlobalGet { } } -impl FromStr for GlobalGet { +impl FromStr for MetadataGet { type Err = Error; /// Parses a string into the command. @@ -160,14 +157,14 @@ impl FromStr for GlobalGet { } } -impl Debug for GlobalGet { +impl Debug for MetadataGet { /// Prints the command as a string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { Display::fmt(self, f) } } -impl Display for GlobalGet { +impl Display for MetadataGet { /// Prints the command to a string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { // Print the command. @@ -179,7 +176,7 @@ impl Display for GlobalGet { } } -impl FromBytes for GlobalGet { +impl FromBytes for MetadataGet { /// Reads the command from a buffer. fn read_le(mut reader: R) -> IoResult { // Read the global ID. @@ -191,7 +188,7 @@ impl FromBytes for GlobalGet { } } -impl ToBytes for GlobalGet { +impl ToBytes for MetadataGet { /// Writes the command to a buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { // Write the global ID. @@ -210,24 +207,24 @@ mod tests { #[test] fn test_parse() { - let (string, global_get) = GlobalGet::::parse("global.get edition into r1;").unwrap(); + let (string, metadata_get) = MetadataGet::::parse("metadata.get edition into r1;").unwrap(); assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); - assert_eq!(global_get.global(), &CallOperator::from_str("edition").unwrap()); - assert_eq!(global_get.destination, Register::Locator(1), "The destination is incorrect"); + assert_eq!(metadata_get.global(), &CallOperator::from_str("edition").unwrap()); + assert_eq!(metadata_get.destination, Register::Locator(1), "The destination is incorrect"); - let (string, global_get) = - GlobalGet::::parse("global.get token.aleo/edition into r1;").unwrap(); + let (string, metadata_get) = + MetadataGet::::parse("metadata.get token.aleo/edition into r1;").unwrap(); assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); - assert_eq!(global_get.global(), &CallOperator::from_str("token.aleo/edition").unwrap()); - assert_eq!(global_get.destination, Register::Locator(1), "The destination is incorrect"); + assert_eq!(metadata_get.global(), &CallOperator::from_str("token.aleo/edition").unwrap()); + assert_eq!(metadata_get.destination, Register::Locator(1), "The destination is incorrect"); } #[test] fn test_from_bytes() { - let (string, get) = GlobalGet::::parse("global.get edition into r1;").unwrap(); + let (string, get) = MetadataGet::::parse("metadata.get edition into r1;").unwrap(); assert!(string.is_empty()); let bytes_le = get.to_bytes_le().unwrap(); - let result = GlobalGet::::from_bytes_le(&bytes_le[..]); + let result = MetadataGet::::from_bytes_le(&bytes_le[..]); assert!(result.is_ok()) } } diff --git a/synthesizer/program/src/logic/command/mod.rs b/synthesizer/program/src/logic/command/mod.rs index 381a151b40..57181c9d45 100644 --- a/synthesizer/program/src/logic/command/mod.rs +++ b/synthesizer/program/src/logic/command/mod.rs @@ -28,8 +28,8 @@ pub use get::*; mod get_or_use; pub use get_or_use::*; -mod global_get; -pub use global_get::*; +mod metadata_get; +pub use metadata_get::*; mod rand_chacha; pub use crate::command::rand_chacha::*; @@ -77,7 +77,7 @@ pub enum Command { /// If the key is not present, `default` is stored `destination`. GetOrUse(GetOrUse), /// Gets the value stored at `global` and stores the result into `destination`. - GlobalGet(GlobalGet), + MetadataGet(MetadataGet), /// Generates a random value using the `rand.chacha` command and stores the result into `destination`. RandChaCha(RandChaCha), /// Removes the (`key`, `value`) entry from the `mapping`. @@ -101,7 +101,7 @@ impl CommandTrait for Command { Command::Contains(contains) => vec![contains.destination().clone()], Command::Get(get) => vec![get.destination().clone()], Command::GetOrUse(get_or_use) => vec![get_or_use.destination().clone()], - Command::GlobalGet(global_get) => vec![global_get.destination().clone()], + Command::MetadataGet(metadata_get) => vec![metadata_get.destination().clone()], Command::RandChaCha(rand_chacha) => vec![rand_chacha.destination().clone()], Command::Await(_) | Command::BranchEq(_) @@ -171,8 +171,8 @@ impl Command { Command::Get(get) => get.finalize(stack, store, registers).map(|_| None), // Finalize the 'get.or_use' command, and return no finalize operation. Command::GetOrUse(get_or_use) => get_or_use.finalize(stack, store, registers).map(|_| None), - // Finalize the 'global.get' command, and return no finalize operation. - Command::GlobalGet(global_get) => global_get.finalize(stack, store, registers).map(|_| None), + // Finalize the 'metadata.get' command, and return no finalize operation. + Command::MetadataGet(metadata_get) => metadata_get.finalize(stack, store, registers).map(|_| None), // Finalize the `rand.chacha` command, and return no finalize operation. Command::RandChaCha(rand_chacha) => rand_chacha.finalize(stack, registers).map(|_| None), // Finalize the 'remove' command, and return the finalize operation. @@ -217,8 +217,8 @@ impl FromBytes for Command { 9 => Ok(Self::BranchNeq(BranchNeq::read_le(&mut reader)?)), // Read the `position` command. 10 => Ok(Self::Position(Position::read_le(&mut reader)?)), - // Read the `global.get` command. - 11 => Ok(Self::GlobalGet(GlobalGet::read_le(&mut reader)?)), + // Read the `metadata.get` command. + 11 => Ok(Self::MetadataGet(MetadataGet::read_le(&mut reader)?)), // Invalid variant. 12.. => Err(error(format!("Invalid command variant: {variant}"))), } @@ -295,11 +295,11 @@ impl ToBytes for Command { // Write the position command. position.write_le(&mut writer) } - Self::GlobalGet(global_get) => { + Self::MetadataGet(metadata_get) => { // Write the variant. 11u8.write_le(&mut writer)?; - // Write the `global.get` command. - global_get.write_le(&mut writer) + // Write the `metadata.get` command. + metadata_get.write_le(&mut writer) } } } @@ -316,7 +316,7 @@ impl Parser for Command { map(Contains::parse, |contains| Self::Contains(contains)), map(GetOrUse::parse, |get_or_use| Self::GetOrUse(get_or_use)), map(Get::parse, |get| Self::Get(get)), - map(GlobalGet::parse, |global_get| Self::GlobalGet(global_get)), + map(MetadataGet::parse, |metadata_get| Self::MetadataGet(metadata_get)), map(RandChaCha::parse, |rand_chacha| Self::RandChaCha(rand_chacha)), map(Remove::parse, |remove| Self::Remove(remove)), map(Set::parse, |set| Self::Set(set)), @@ -362,7 +362,7 @@ impl Display for Command { Self::Contains(contains) => Display::fmt(contains, f), Self::Get(get) => Display::fmt(get, f), Self::GetOrUse(get_or_use) => Display::fmt(get_or_use, f), - Self::GlobalGet(global_get) => Display::fmt(global_get, f), + Self::MetadataGet(metadata_get) => Display::fmt(metadata_get, f), Self::RandChaCha(rand_chacha) => Display::fmt(rand_chacha, f), Self::Remove(remove) => Display::fmt(remove, f), Self::Set(set) => Display::fmt(set, f), @@ -420,8 +420,8 @@ mod tests { let bytes = command.to_bytes_le().unwrap(); assert_eq!(command, Command::from_bytes_le(&bytes).unwrap()); - // GlobalGet - let expected = "global.get edition into r0;"; + // MetadataGet + let expected = "metadata.get edition into r0;"; let command = Command::::parse(expected).unwrap().1; let bytes = command.to_bytes_le().unwrap(); assert_eq!(command, Command::from_bytes_le(&bytes).unwrap()); @@ -503,10 +503,10 @@ mod tests { assert_eq!(Command::GetOrUse(GetOrUse::from_str(expected).unwrap()), command); assert_eq!(expected, command.to_string()); - // GlobalGet - let expected = "global.get edition into r0;"; + // MetadataGet + let expected = "metadata.get edition into r0;"; let command = Command::::parse(expected).unwrap().1; - assert_eq!(Command::GlobalGet(GlobalGet::from_str(expected).unwrap()), command); + assert_eq!(Command::MetadataGet(MetadataGet::from_str(expected).unwrap()), command); assert_eq!(expected, command.to_string()); // RandChaCha diff --git a/synthesizer/src/vm/tests/test_vm_update.rs b/synthesizer/src/vm/tests/test_vm_update.rs index 457979bba2..52a0570507 100644 --- a/synthesizer/src/vm/tests/test_vm_update.rs +++ b/synthesizer/src/vm/tests/test_vm_update.rs @@ -745,7 +745,7 @@ function sum: output r2 as u8.public; output r3 as dependent.aleo/sum.future; finalize sum: - global.get dependency.aleo/edition into r0; + metadata.get dependency.aleo/edition into r0; assert.eq r0 0u16; function sum_and_check: @@ -780,7 +780,7 @@ function sum: output r2 as u8.public; output r3 as dependent.aleo/sum.future; finalize sum: - global.get dependency.aleo/edition into r0; + metadata.get dependency.aleo/edition into r0; assert.eq r0 1u16; function sum_and_check: diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/global_get.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/global_get.aleo index ed9229bcce..c6695854b8 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/global_get.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/global_get.aleo @@ -23,7 +23,7 @@ function a: output r1 as first.aleo/a.future; finalize a: input r0 as u16.public; - global.get edition into r1; + metadata.get edition into r1; assert.eq r0 r1; ///////////////////////////////////////////////// @@ -36,5 +36,5 @@ function a: output r1 as second.aleo/a.future; finalize a: input r0 as u16.public; - global.get edition into r1; + metadata.get edition into r1; assert.eq r0 r1; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo index f7e02ac4d8..083410a83d 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/invalid_global_fail.aleo @@ -11,6 +11,6 @@ function a: output r1 as first.aleo/a.future; finalize a: input r0 as u16.public; - global.get foo into r1; + metadata.get foo into r1; assert.eq r0 r1; From 2c94a0272d6b325411b47474773cb5d44f477ebd Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:07:32 -0800 Subject: [PATCH 37/46] Add test for record consumption --- ledger/src/tests.rs | 227 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index d3aacbd33c..07c22beab0 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -3142,3 +3142,230 @@ fn test_forged_block_subdags() { assert!(ledger.check_next_block(&forged_block_2_from_both_subdags, &mut rand::thread_rng()).is_err()); } } + +#[test] +fn test_record_creation_and_consumption_in_call() { + let rng = &mut TestRng::default(); + + // Sample the test environment. + let crate::test_helpers::TestEnv { ledger, private_key, view_key, .. } = crate::test_helpers::sample_test_env(rng); + + // A helper function to get the record counts. + let get_record_counts = || { + let slow_spent_filter = RecordsFilter::SlowSpent(private_key.clone()); + let slow_unspent_filter = RecordsFilter::SlowUnspent(private_key.clone()); + let spent_records = ledger.find_records(&view_key, RecordsFilter::Spent).unwrap().collect_vec().len(); + let slow_spent_records = ledger.find_records(&view_key, slow_spent_filter).unwrap().collect_vec().len(); + let unspent_records = ledger.find_records(&view_key, RecordsFilter::Unspent).unwrap().collect_vec().len(); + let slow_unspent_records = ledger.find_records(&view_key, slow_unspent_filter).unwrap().collect_vec().len(); + let records = ledger.records().collect_vec().len(); + (spent_records, slow_spent_records, unspent_records, slow_unspent_records, records) + }; + + // Check the initial record counts. + let (initial_spent_records, initial_slow_spent_records, initial_unspent_records, initial_slow_unspent_records, initial_records) = get_record_counts(); + assert_eq!(0, initial_spent_records); + assert_eq!(0, initial_slow_spent_records); + assert_eq!(4, initial_unspent_records); + assert_eq!(4, initial_slow_unspent_records); + assert_eq!(4, initial_records); + + // Initialize the two programs. + let program_0 = Program::from_str( + r" +program child.aleo; + +record data: + owner as address.private; + val as u64.private; + +function mint: + cast self.signer 0u64 into r0 as data.record; + output r0 as data.record; + +function burn: + input r0 as data.record; + ").unwrap(); + + let program_1 = Program::from_str( + r" +import child.aleo; + +program parent.aleo; + +function create_without_output: + call child.aleo/mint into r0; + +function create: + call child.aleo/mint into r0; + output r0 as child.aleo/data.record; + +function consume_without_call: + input r0 as child.aleo/data.record; + +function consume: + input r0 as child.aleo/data.record; + call child.aleo/burn r0; + +function create_and_consume: + call child.aleo/mint into r0; + call child.aleo/burn r0; + ").unwrap(); + + // Deploy the programs. + let deployment_0 = ledger.vm().deploy(&private_key, &program_0, None, 0, None, rng).unwrap(); + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_0], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + let deployment_1 = ledger.vm().deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_1], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Call the `mint` function. + let transaction = ledger.vm() + .execute( + &private_key, + ("child.aleo", "mint"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let record = transaction.records().last().unwrap().1.decrypt(&view_key).unwrap(); + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Check the record counts. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records); + assert_eq!(num_slow_spent_records, initial_slow_spent_records); + assert_eq!(num_unspent_records, initial_unspent_records + 1); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); + assert_eq!(num_records, initial_records + 1); + + // Call the `create_without_output` function. + let transaction = ledger.vm() + .execute( + &private_key, + ("parent.aleo", "create_without_output"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Ensure that no new records were created or spent for the view key. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records); + assert_eq!(num_slow_spent_records, initial_slow_spent_records); + assert_eq!(num_unspent_records, initial_unspent_records + 2); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); + assert_eq!(num_records, initial_records + 2); + + // Call the `create` function. + let transaction = ledger.vm() + .execute( + &private_key, + ("parent.aleo", "create"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Ensure that a record was created and spent. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records); + assert_eq!(num_slow_spent_records, initial_slow_spent_records); + assert_eq!(num_unspent_records, initial_unspent_records + 3); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 3); + assert_eq!(num_records, initial_records + 3); + + // Call the `consume_without_call` function. + let transaction = ledger.vm() + .execute( + &private_key, + ("parent.aleo", "consume_without_call"), + vec![Value::Record(record.clone())].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Ensure that no records were created or spent. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records); + assert_eq!(num_slow_spent_records, initial_slow_spent_records); + assert_eq!(num_unspent_records, initial_unspent_records + 3); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 3); + assert_eq!(num_records, initial_records + 3); + + // Call the `consume` function. + let transaction = ledger.vm() + .execute( + &private_key, + ("parent.aleo", "consume"), + vec![Value::Record(record)].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Ensure that the record was spent. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records + 1); + assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); + assert_eq!(num_unspent_records, initial_unspent_records + 2); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); + assert_eq!(num_records, initial_records + 3); + + // Call the `create_and_consume` function. + let transaction = ledger.vm() + .execute( + &private_key, + ("parent.aleo", "create_and_consume"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Ensure that a record was created and spent. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records + 2); + assert_eq!(num_slow_spent_records, initial_slow_spent_records + 2); + assert_eq!(num_unspent_records, initial_unspent_records + 2); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); + assert_eq!(num_records, initial_records + 4); +} From bcac67f235f6154856e473bc33167dfe18908c5d Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:01:51 -0800 Subject: [PATCH 38/46] Fix impl --- ledger/block/src/transaction/merkle.rs | 2 +- ledger/src/tests.rs | 158 +++++++++++------- synthesizer/process/src/cost.rs | 49 +++--- synthesizer/process/src/lib.rs | 2 +- .../process/src/stack/authorization/mod.rs | 2 +- .../process/src/stack/helpers/initialize.rs | 6 + synthesizer/process/src/stack/mod.rs | 40 +++-- synthesizer/process/src/verify_execution.rs | 6 +- 8 files changed, 156 insertions(+), 109 deletions(-) diff --git a/ledger/block/src/transaction/merkle.rs b/ledger/block/src/transaction/merkle.rs index d6e5d4fa56..0a15631e65 100644 --- a/ledger/block/src/transaction/merkle.rs +++ b/ledger/block/src/transaction/merkle.rs @@ -214,7 +214,7 @@ impl Transaction { // Ensure the number of functions is within the allowed range. ensure!( num_transitions < Self::MAX_TRANSITIONS, // Note: Observe we hold back 1 for the fee. - "Execution must contain less than {num_transitions} transitions, found {}", + "Execution must contain less than {} transitions, found {num_transitions}", Self::MAX_TRANSITIONS, ); Ok(()) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 07c22beab0..800d8ec028 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -3152,8 +3152,8 @@ fn test_record_creation_and_consumption_in_call() { // A helper function to get the record counts. let get_record_counts = || { - let slow_spent_filter = RecordsFilter::SlowSpent(private_key.clone()); - let slow_unspent_filter = RecordsFilter::SlowUnspent(private_key.clone()); + let slow_spent_filter = RecordsFilter::SlowSpent(private_key); + let slow_unspent_filter = RecordsFilter::SlowUnspent(private_key); let spent_records = ledger.find_records(&view_key, RecordsFilter::Spent).unwrap().collect_vec().len(); let slow_spent_records = ledger.find_records(&view_key, slow_spent_filter).unwrap().collect_vec().len(); let unspent_records = ledger.find_records(&view_key, RecordsFilter::Unspent).unwrap().collect_vec().len(); @@ -3163,7 +3163,13 @@ fn test_record_creation_and_consumption_in_call() { }; // Check the initial record counts. - let (initial_spent_records, initial_slow_spent_records, initial_unspent_records, initial_slow_unspent_records, initial_records) = get_record_counts(); + let ( + initial_spent_records, + initial_slow_spent_records, + initial_unspent_records, + initial_slow_unspent_records, + initial_records, + ) = get_record_counts(); assert_eq!(0, initial_spent_records); assert_eq!(0, initial_slow_spent_records); assert_eq!(4, initial_unspent_records); @@ -3185,7 +3191,9 @@ function mint: function burn: input r0 as data.record; - ").unwrap(); + ", + ) + .unwrap(); let program_1 = Program::from_str( r" @@ -3210,38 +3218,37 @@ function consume: function create_and_consume: call child.aleo/mint into r0; call child.aleo/burn r0; - ").unwrap(); + ", + ) + .unwrap(); // Deploy the programs. let deployment_0 = ledger.vm().deploy(&private_key, &program_0, None, 0, None, rng).unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_0], rng).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_0], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); let deployment_1 = ledger.vm().deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_1], rng).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_1], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); // Call the `mint` function. - let transaction = ledger.vm() - .execute( - &private_key, - ("child.aleo", "mint"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) + let transaction = ledger + .vm() + .execute(&private_key, ("child.aleo", "mint"), Vec::>::new().iter(), None, 0, None, rng) .unwrap(); - let record = transaction.records().last().unwrap().1.decrypt(&view_key).unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + let mint_record = transaction.records().last().unwrap().1.decrypt(&view_key).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); // Check the record counts. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = + get_record_counts(); assert_eq!(num_spent_records, initial_spent_records); assert_eq!(num_slow_spent_records, initial_slow_spent_records); assert_eq!(num_unspent_records, initial_unspent_records + 1); @@ -3249,7 +3256,8 @@ function create_and_consume: assert_eq!(num_records, initial_records + 1); // Call the `create_without_output` function. - let transaction = ledger.vm() + let transaction = ledger + .vm() .execute( &private_key, ("parent.aleo", "create_without_output"), @@ -3261,20 +3269,43 @@ function create_and_consume: ) .unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); - // Ensure that no new records were created or spent for the view key. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); + // Check the record counts. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = + get_record_counts(); assert_eq!(num_spent_records, initial_spent_records); assert_eq!(num_slow_spent_records, initial_slow_spent_records); assert_eq!(num_unspent_records, initial_unspent_records + 2); assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); assert_eq!(num_records, initial_records + 2); + // Call the `burn` function on record created by `create_without_output`. + let record = block.records().collect_vec().last().unwrap().1.decrypt(&view_key).unwrap(); + let transaction = ledger + .vm() + .execute(&private_key, ("child.aleo", "burn"), vec![Value::Record(record)].iter(), None, 0, None, rng) + .unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); + + // Check the record counts. + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = + get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records + 1); + assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); + assert_eq!(num_unspent_records, initial_unspent_records + 1); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); + assert_eq!(num_records, initial_records + 2); + // Call the `create` function. - let transaction = ledger.vm() + let transaction = ledger + .vm() .execute( &private_key, ("parent.aleo", "create"), @@ -3285,68 +3316,69 @@ function create_and_consume: rng, ) .unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); // Ensure that a record was created and spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records); - assert_eq!(num_slow_spent_records, initial_slow_spent_records); - assert_eq!(num_unspent_records, initial_unspent_records + 3); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 3); + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = + get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records + 1); + assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); + assert_eq!(num_unspent_records, initial_unspent_records + 2); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); assert_eq!(num_records, initial_records + 3); // Call the `consume_without_call` function. - let transaction = ledger.vm() + let transaction = ledger + .vm() .execute( &private_key, ("parent.aleo", "consume_without_call"), - vec![Value::Record(record.clone())].iter(), + vec![Value::Record(mint_record.clone())].iter(), None, 0, None, rng, ) .unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); // Ensure that no records were created or spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records); - assert_eq!(num_slow_spent_records, initial_slow_spent_records); - assert_eq!(num_unspent_records, initial_unspent_records + 3); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 3); + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = + get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records + 1); + assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); + assert_eq!(num_unspent_records, initial_unspent_records + 2); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); assert_eq!(num_records, initial_records + 3); // Call the `consume` function. - let transaction = ledger.vm() - .execute( - &private_key, - ("parent.aleo", "consume"), - vec![Value::Record(record)].iter(), - None, - 0, - None, - rng, - ) + let transaction = ledger + .vm() + .execute(&private_key, ("parent.aleo", "consume"), vec![Value::Record(mint_record)].iter(), None, 0, None, rng) .unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); // Ensure that the record was spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records + 1); - assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); - assert_eq!(num_unspent_records, initial_unspent_records + 2); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = + get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records + 2); + assert_eq!(num_slow_spent_records, initial_slow_spent_records + 2); + assert_eq!(num_unspent_records, initial_unspent_records + 1); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); assert_eq!(num_records, initial_records + 3); // Call the `create_and_consume` function. - let transaction = ledger.vm() + let transaction = ledger + .vm() .execute( &private_key, ("parent.aleo", "create_and_consume"), @@ -3357,15 +3389,17 @@ function create_and_consume: rng, ) .unwrap(); - let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); ledger.advance_to_next_block(&block).unwrap(); // Ensure that a record was created and spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records + 2); - assert_eq!(num_slow_spent_records, initial_slow_spent_records + 2); - assert_eq!(num_unspent_records, initial_unspent_records + 2); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); + let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = + get_record_counts(); + assert_eq!(num_spent_records, initial_spent_records + 3); + assert_eq!(num_slow_spent_records, initial_slow_spent_records + 3); + assert_eq!(num_unspent_records, initial_unspent_records + 1); + assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); assert_eq!(num_records, initial_records + 4); } diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 71cf773375..04c6cae177 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -13,8 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Process, Stack, StackProgramTypes}; -use std::sync::Arc; +use crate::{Process, Stack, StackProgramTypes, StackRef}; use console::{ prelude::*, @@ -410,13 +409,6 @@ pub fn cost_per_command( pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Identifier) -> Result { // Initialize the base cost. let mut finalize_cost = 0u64; - // A helper enum to track stacks. - enum StackRef<'a, N: Network> { - // Self's stack. - Internal(&'a Stack), - // An external stack. - External(Arc>), - } // Initialize a queue of finalize blocks to tally. let mut finalizes = vec![(StackRef::Internal(stack), *function_name)]; // Initialize a counter for the number of finalize blocks seen. @@ -425,27 +417,31 @@ pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Ide while let Some((stack_ref, function_name)) = finalizes.pop() { // Ensure that the number of finalize blocks does not exceed the maximum. // Note that one transition is reserved for the fee. - ensure!(num_finalizes < Transaction::::MAX_TRANSITIONS, "The number of finalize blocks cannot "); + ensure!( + num_finalizes < Transaction::::MAX_TRANSITIONS, + "The number of finalize blocks must be less than '{}'", + Transaction::::MAX_TRANSITIONS + ); // Get the finalize logic. If the function does not have a finalize scope then no cost is incurred. if let Some(finalize) = match &stack_ref { - StackRef::Internal(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), - StackRef::External(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), + StackRef::Internal(stack_ref) => stack_ref.get_function_ref(&function_name)?.finalize_logic(), + StackRef::External(stack_ref) => stack_ref.get_function_ref(&function_name)?.finalize_logic(), } { // Queue the futures to be tallied. for input in finalize.inputs() { if let FinalizeType::Future(future) = input.finalize_type() { // Get the external stack for the future. - let stack = stack.get_external_stack(future.program_id())?; + let external_stack = stack_ref.get_external_stack(future.program_id())?; // Increment the number of finalize blocks seen. num_finalizes += 1; // Queue the future. - finalizes.push((StackRef::External(stack), *future.resource())); + finalizes.push((StackRef::External(external_stack), *future.resource())); } } // Sum the cost of all commands in the current block. for command in finalize.commands() { finalize_cost = finalize_cost - .checked_add(cost_per_command(stack, finalize, command, ConsensusFeeVersion::V2)?) + .checked_add(cost_per_command(&stack_ref, finalize, command, ConsensusFeeVersion::V2)?) .ok_or(anyhow!("Finalize cost overflowed"))?; } } @@ -457,13 +453,6 @@ pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Ide pub fn cost_in_microcredits_v1(stack: &Stack, function_name: &Identifier) -> Result { // Initialize the base cost. let mut finalize_cost = 0u64; - // A helper enum to track stacks. - enum StackRef<'a, N: Network> { - // Self's stack. - Internal(&'a Stack), - // An external stack. - External(Arc>), - } // Initialize a queue of finalize blocks to tally. let mut finalizes = vec![(StackRef::Internal(stack), *function_name)]; // Initialize a counter for the number of finalize blocks seen. @@ -472,27 +461,31 @@ pub fn cost_in_microcredits_v1(stack: &Stack, function_name: &Ide while let Some((stack_ref, function_name)) = finalizes.pop() { // Ensure that the number of finalize blocks does not exceed the maximum. // Note that one transition is reserved for the fee. - ensure!(num_finalizes < Transaction::::MAX_TRANSITIONS, "The number of finalize blocks cannot "); + ensure!( + num_finalizes < Transaction::::MAX_TRANSITIONS, + "The number of finalize blocks must be less than '{}'", + Transaction::::MAX_TRANSITIONS + ); // Get the finalize logic. If the function does not have a finalize scope then no cost is incurred. if let Some(finalize) = match &stack_ref { - StackRef::Internal(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), - StackRef::External(stack) => stack.get_function_ref(&function_name)?.finalize_logic(), + StackRef::Internal(stack_ref) => stack_ref.get_function_ref(&function_name)?.finalize_logic(), + StackRef::External(stack_ref) => stack_ref.get_function_ref(&function_name)?.finalize_logic(), } { // Queue the futures to be tallied. for input in finalize.inputs() { if let FinalizeType::Future(future) = input.finalize_type() { // Get the external stack for the future. - let stack = stack.get_external_stack(future.program_id())?; + let external_stack = stack_ref.get_external_stack(future.program_id())?; // Increment the number of finalize blocks seen. num_finalizes += 1; // Queue the future. - finalizes.push((StackRef::External(stack), *future.resource())); + finalizes.push((StackRef::External(external_stack), *future.resource())); } } // Sum the cost of all commands in the current block. for command in finalize.commands() { finalize_cost = finalize_cost - .checked_add(cost_per_command(stack, finalize, command, ConsensusFeeVersion::V1)?) + .checked_add(cost_per_command(&stack_ref, finalize, command, ConsensusFeeVersion::V1)?) .ok_or(anyhow!("Finalize cost overflowed"))?; } } diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index bd795d68b4..dfe7bbafbf 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -49,7 +49,7 @@ use console::{ program::{Identifier, Literal, Locator, Plaintext, ProgramID, Record, Response, Value, compute_function_id}, types::{Field, U16, U64}, }; -use ledger_block::{Deployment, Execution, Fee, Input, Output, Transition}; +use ledger_block::{Deployment, Execution, Fee, Input, Output, Transaction, Transition}; use ledger_store::{FinalizeStorage, FinalizeStore, atomic_batch_scope}; use synthesizer_program::{ Branch, diff --git a/synthesizer/process/src/stack/authorization/mod.rs b/synthesizer/process/src/stack/authorization/mod.rs index e86c2cb74e..2bc5025f67 100644 --- a/synthesizer/process/src/stack/authorization/mod.rs +++ b/synthesizer/process/src/stack/authorization/mod.rs @@ -149,7 +149,7 @@ impl Authorization { // Check that the number of requests is less than the maximum. ensure!( self.len() < Transaction::::MAX_TRANSITIONS, - "The number of requests in the authorization cannot exceed '{}'.", + "The number of requests in the authorization must be less than '{}'.", Transaction::::MAX_TRANSITIONS ); // Append the request to the authorization. diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 421e43a0e7..598030acc1 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -45,12 +45,16 @@ impl Stack { } } + println!("ONE"); + // Add the program closures to the stack. for closure in program.closures().values() { // Add the closure to the stack. stack.insert_closure(closure)?; } + println!("TWO"); + // Add the program functions to the stack. for function in program.functions().values() { // Add the function to the stack. @@ -60,6 +64,8 @@ impl Stack { stack.get_number_of_calls(function.name())?; } + println!("THREE"); + // Return the stack. Ok(stack) } diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 6580c14263..9f78327a7c 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -139,7 +139,7 @@ impl CallStack { // Check that the number of requests does not exceed the maximum. ensure!( requests.len() < Transaction::::MAX_TRANSITIONS, - "The number of requests in the authorization cannot exceed '{}'.", + "The number of requests in the authorization must be less than '{}'.", Transaction::::MAX_TRANSITIONS ); // Push the request to the stack. @@ -221,6 +221,7 @@ impl Stack { } } }; + println!("Program edition: {}", edition); // Ensure the program contains functions. ensure!(!program.functions().is_empty(), "No functions present in the deployment for program '{program_id}'"); // Serialize the program into bytes. @@ -293,13 +294,6 @@ impl StackProgram for Stack { fn get_number_of_calls(&self, function_name: &Identifier) -> Result { // Initialize the base number of calls. let mut num_calls = 1; - // A helper enum to track stacks. - enum StackRef<'a, N: Network> { - // Self's stack. - Internal(&'a Stack), - // An external stack. - External(Arc>), - } // Initialize a queue of functions to check. let mut queue = vec![(StackRef::Internal(self), *function_name)]; // Iterate over the queue. @@ -308,9 +302,9 @@ impl StackProgram for Stack { // Note that one transition is reserved for the fee. ensure!( num_calls < Transaction::::MAX_TRANSITIONS, - "Number of calls exceeds the maximum allowed number of transitions" + "Number of calls must be less than '{}'", + Transaction::::MAX_TRANSITIONS ); - // Determine the number of calls for the function. for instruction in match &stack_ref { StackRef::Internal(stack) => stack.get_function_ref(&function_name)?.instructions(), @@ -318,19 +312,19 @@ impl StackProgram for Stack { } { if let Instruction::Call(call) = instruction { // Determine if this is a function call. - if call.is_function_call(self)? { + if call.is_function_call(&*stack_ref)? { // Increment by the number of calls. num_calls += 1; // Add the function to the queue. match call.operator() { CallOperator::Locator(locator) => { queue.push(( - StackRef::External(self.get_external_stack(locator.program_id())?), + StackRef::External(stack_ref.get_external_stack(locator.program_id())?), *locator.resource(), )); } CallOperator::Resource(resource) => { - queue.push((StackRef::Internal(self), *resource)); + queue.push((stack_ref.clone(), *resource)); } } } @@ -499,3 +493,23 @@ impl PartialEq for Stack { } impl Eq for Stack {} + +// A helper enum to avoid cloning stacks. +#[derive(Clone)] +pub(crate) enum StackRef<'a, N: Network> { + // Self's stack. + Internal(&'a Stack), + // An external stack. + External(Arc>), +} + +impl Deref for StackRef<'_, N> { + type Target = Stack; + + fn deref(&self) -> &Self::Target { + match self { + StackRef::Internal(stack) => stack, + StackRef::External(stack) => stack, + } + } +} diff --git a/synthesizer/process/src/verify_execution.rs b/synthesizer/process/src/verify_execution.rs index e7bbea558c..e8d96179d3 100644 --- a/synthesizer/process/src/verify_execution.rs +++ b/synthesizer/process/src/verify_execution.rs @@ -14,7 +14,6 @@ // limitations under the License. use super::*; -use ledger_block::Transaction; impl Process { /// Verifies the given execution is valid. @@ -27,8 +26,9 @@ impl Process { ensure!(!execution.is_empty(), "There are no transitions in the execution"); // Ensure that the execution does not exceed the maximum number of transitions. ensure!( - execution.len() <= Transaction::::MAX_TRANSITIONS, - "The execution exceeded the maximum number of transitions" + execution.len() < Transaction::::MAX_TRANSITIONS, + "The number of transitions in an execution must be less than '{}'", + Transaction::::MAX_TRANSITIONS ); // Ensure the number of transitions matches the program function. From 7c61ac4eabd6eacbac2017091034d8fc879234bf Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 14 Feb 2025 07:17:03 -0800 Subject: [PATCH 39/46] Fix failing test, move original vm tests back for reviewability --- synthesizer/src/vm/mod.rs | 1735 ++++++++++++++++- synthesizer/src/vm/tests/mod.rs | 9 +- synthesizer/src/vm/tests/test_vm_standard.rs | 1736 ------------------ synthesizer/src/vm/verify.rs | 26 +- 4 files changed, 1756 insertions(+), 1750 deletions(-) delete mode 100644 synthesizer/src/vm/tests/test_vm_standard.rs diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 36063445a7..14e5d94617 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -473,14 +473,16 @@ pub(crate) mod test_helpers { use console::{ account::{Address, ViewKey}, network::MainnetV0, - program::Value, + program::{Entry, Value}, types::Field, }; use ledger_block::{Block, Header, Metadata, Transition}; use ledger_store::helpers::memory::ConsensusMemory; #[cfg(feature = "rocks")] use ledger_store::helpers::rocksdb::ConsensusDB; + use ledger_test_helpers::{large_transaction_program, small_transaction_program}; use synthesizer_program::Program; + use synthesizer_snark::VerifyingKey; use indexmap::IndexMap; use once_cell::sync::OnceCell; @@ -846,4 +848,1735 @@ function compute: rng, ) } + + #[test] + fn test_multiple_deployments_and_multiple_executions() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + + // Select a record to spend. + let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Split once. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "split"), + [Value::Record(record), Value::from_str("1000000000u64").unwrap()].iter(), // 1000 credits + None, + 0, + None, + rng, + ) + .unwrap(); + let records = transaction.records().collect_vec(); + let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); + let second_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + vm.add_next_block(&block).unwrap(); + + // Split again. + let mut transactions = Vec::new(); + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "split"), + [Value::Record(first_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits + None, + 0, + None, + rng, + ) + .unwrap(); + let records = transaction.records().collect_vec(); + let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); + let third_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); + transactions.push(transaction); + // Split again. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "split"), + [Value::Record(second_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits + None, + 0, + None, + rng, + ) + .unwrap(); + let records = transaction.records().collect_vec(); + let second_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); + let fourth_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); + transactions.push(transaction); + // Add the split transactions to a block and update the VM. + let fee_block = sample_next_block(&vm, &caller_private_key, &transactions, rng).unwrap(); + vm.add_next_block(&fee_block).unwrap(); + + // Deploy the programs. + let first_program = r" +program test_program_1.aleo; +mapping map_0: + key as field.public; + value as field.public; +function init: + async init into r0; + output r0 as test_program_1.aleo/init.future; +finalize init: + set 0field into map_0[0field]; +function getter: + async getter into r0; + output r0 as test_program_1.aleo/getter.future; +finalize getter: + get map_0[0field] into r0; + "; + let second_program = r" +program test_program_2.aleo; +mapping map_0: + key as field.public; + value as field.public; +function init: + async init into r0; + output r0 as test_program_2.aleo/init.future; +finalize init: + set 0field into map_0[0field]; +function getter: + async getter into r0; + output r0 as test_program_2.aleo/getter.future; +finalize getter: + get map_0[0field] into r0; + "; + let first_deployment = vm + .deploy(&caller_private_key, &Program::from_str(first_program).unwrap(), Some(first_record), 1, None, rng) + .unwrap(); + let second_deployment = vm + .deploy(&caller_private_key, &Program::from_str(second_program).unwrap(), Some(second_record), 1, None, rng) + .unwrap(); + let deployment_block = + sample_next_block(&vm, &caller_private_key, &[first_deployment, second_deployment], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Execute the programs. + let first_execution = vm + .execute( + &caller_private_key, + ("test_program_1.aleo", "init"), + Vec::>::new().iter(), + Some(third_record), + 1, + None, + rng, + ) + .unwrap(); + let second_execution = vm + .execute( + &caller_private_key, + ("test_program_2.aleo", "init"), + Vec::>::new().iter(), + Some(fourth_record), + 1, + None, + rng, + ) + .unwrap(); + let execution_block = + sample_next_block(&vm, &caller_private_key, &[first_execution, second_execution], rng).unwrap(); + vm.add_next_block(&execution_block).unwrap(); + } + + #[test] + fn test_load_deployments_with_imports() { + // NOTE: This seed was chosen for the CI's RNG to ensure that the test passes. + let rng = &mut TestRng::fixed(123456789); + + // Initialize a new caller. + let caller_private_key = PrivateKey::::new(rng).unwrap(); + let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); + + // Initialize the VM. + let vm = sample_vm(); + // Initialize the genesis block. + let genesis = vm.genesis_beacon(&caller_private_key, rng).unwrap(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + let record_0 = records[0].1.decrypt(&caller_view_key).unwrap(); + let record_1 = records[1].1.decrypt(&caller_view_key).unwrap(); + let record_2 = records[2].1.decrypt(&caller_view_key).unwrap(); + let record_3 = records[3].1.decrypt(&caller_view_key).unwrap(); + + // Create the deployment for the first program. + let program_1 = r" +program first_program.aleo; + +function c: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_1 = vm + .deploy(&caller_private_key, &Program::from_str(program_1).unwrap(), Some(record_0), 0, None, rng) + .unwrap(); + + // Deploy the first program. + let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_1.clone()], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Create the deployment for the second program. + let program_2 = r" +import first_program.aleo; + +program second_program.aleo; + +function b: + input r0 as u8.private; + input r1 as u8.private; + call first_program.aleo/c r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_2 = vm + .deploy(&caller_private_key, &Program::from_str(program_2).unwrap(), Some(record_1), 0, None, rng) + .unwrap(); + + // Deploy the second program. + let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_2.clone()], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Create the deployment for the third program. + let program_3 = r" +import second_program.aleo; + +program third_program.aleo; + +function a: + input r0 as u8.private; + input r1 as u8.private; + call second_program.aleo/b r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_3 = vm + .deploy(&caller_private_key, &Program::from_str(program_3).unwrap(), Some(record_2), 0, None, rng) + .unwrap(); + + // Create the deployment for the fourth program. + let program_4 = r" +import second_program.aleo; +import first_program.aleo; + +program fourth_program.aleo; + +function a: + input r0 as u8.private; + input r1 as u8.private; + call second_program.aleo/b r0 r1 into r2; + output r2 as u8.private; + "; + let deployment_4 = vm + .deploy(&caller_private_key, &Program::from_str(program_4).unwrap(), Some(record_3), 0, None, rng) + .unwrap(); + + // Deploy the third and fourth program together. + let deployment_block = + sample_next_block(&vm, &caller_private_key, &[deployment_3.clone(), deployment_4.clone()], rng).unwrap(); + vm.add_next_block(&deployment_block).unwrap(); + + // Sanity check the ordering of the deployment transaction IDs from storage. + { + let deployment_transaction_ids = + vm.transaction_store().deployment_transaction_ids().map(|id| *id).collect::>(); + // This assert check is here to ensure that we are properly loading imports even though any order will work for `VM::from`. + // Note: `deployment_transaction_ids` is sorted lexicographically by transaction ID, so the order may change if we update internal methods. + assert_eq!( + deployment_transaction_ids, + vec![deployment_4.id(), deployment_3.id(), deployment_2.id(), deployment_1.id()], + "Update me if serialization has changed" + ); + } + + // Enforce that the VM can load properly with the imports. + assert!(VM::from(vm.store.clone()).is_ok()); + } + + #[test] + fn test_multiple_external_calls() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); + let address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Fetch the unspent records. + let records = + genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + let record_0 = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); + let record_1 = records.values().nth(1).unwrap().decrypt(&caller_view_key).unwrap(); + let record_2 = records.values().nth(2).unwrap().decrypt(&caller_view_key).unwrap(); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the program. + let program = Program::from_str( + r" +import credits.aleo; + +program test_multiple_external_calls.aleo; + +function multitransfer: + input r0 as credits.aleo/credits.record; + input r1 as address.private; + input r2 as u64.private; + call credits.aleo/transfer_private r0 r1 r2 into r3 r4; + call credits.aleo/transfer_private r4 r1 r2 into r5 r6; + output r4 as credits.aleo/credits.record; + output r5 as credits.aleo/credits.record; + output r6 as credits.aleo/credits.record; + ", + ) + .unwrap(); + let deployment = vm.deploy(&caller_private_key, &program, Some(record_0), 1, None, rng).unwrap(); + vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap()).unwrap(); + + // Execute the programs. + let inputs = [ + Value::::Record(record_1), + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("10u64").unwrap(), + ]; + let execution = vm + .execute( + &caller_private_key, + ("test_multiple_external_calls.aleo", "multitransfer"), + inputs.into_iter(), + Some(record_2), + 1, + None, + rng, + ) + .unwrap(); + vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[execution], rng).unwrap()).unwrap(); + } + + #[test] + fn test_nested_deployment_with_assert() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program child_program.aleo; + +function check: + input r0 as field.private; + assert.eq r0 123456789123456789123456789123456789123456789123456789field; + ", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Check that program is deployed. + assert!(vm.contains_program(&ProgramID::from_str("child_program.aleo").unwrap())); + + // Deploy the program that calls the program from the previous layer. + let program = Program::from_str( + r" +import child_program.aleo; + +program parent_program.aleo; + +function check: + input r0 as field.private; + call child_program.aleo/check r0; + ", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Check that program is deployed. + assert!(vm.contains_program(&ProgramID::from_str("parent_program.aleo").unwrap())); + } + + #[test] + fn test_deployment_with_external_records() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the program. + let program = Program::from_str( + r" +import credits.aleo; +program test_program.aleo; + +function transfer: + input r0 as credits.aleo/credits.record; + input r1 as u64.private; + input r2 as u64.private; + input r3 as [address; 10u32].private; + call credits.aleo/transfer_private r0 r3[0u32] r1 into r4 r5; + call credits.aleo/transfer_private r5 r3[0u32] r2 into r6 r7; +", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Check that program is deployed. + assert!(vm.contains_program(&ProgramID::from_str("test_program.aleo").unwrap())); + } + + #[test] + fn test_internal_fee_calls_are_invalid() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let view_key = ViewKey::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Fetch the unspent records. + let records = + genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + let record_0 = records.values().next().unwrap().decrypt(&view_key).unwrap(); + + // Deploy the program. + let program = Program::from_str( + r" +import credits.aleo; +program test_program.aleo; + +function call_fee_public: + input r0 as u64.private; + input r1 as u64.private; + input r2 as field.private; + call credits.aleo/fee_public r0 r1 r2 into r3; + async call_fee_public r3 into r4; + output r4 as test_program.aleo/call_fee_public.future; + +finalize call_fee_public: + input r0 as credits.aleo/fee_public.future; + await r0; + +function call_fee_private: + input r0 as credits.aleo/credits.record; + input r1 as u64.private; + input r2 as u64.private; + input r3 as field.private; + call credits.aleo/fee_private r0 r1 r2 r3 into r4; + output r4 as credits.aleo/credits.record; +", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_ok()); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // Execute the programs. + let internal_base_fee_amount: u64 = rng.gen_range(1..1000); + let internal_priority_fee_amount: u64 = rng.gen_range(1..1000); + + // Ensure that the transaction that calls `fee_public` internally cannot be generated. + let inputs = [ + Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), + Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), + Value::::from_str("1field").unwrap(), + ]; + assert!( + vm.execute(&private_key, ("test_program.aleo", "call_fee_public"), inputs.into_iter(), None, 0, None, rng) + .is_err() + ); + + // Ensure that the transaction that calls `fee_private` internally cannot be generated. + let inputs = [ + Value::::Record(record_0), + Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), + Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), + Value::::from_str("1field").unwrap(), + ]; + assert!( + vm.execute(&private_key, ("test_program.aleo", "call_fee_private"), inputs.into_iter(), None, 0, None, rng) + .is_err() + ); + } + + #[test] + #[ignore = "memory-intensive"] + fn test_deployment_synthesis_overload() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_overload.aleo; + +function do: + input r0 as [[u128; 32u32]; 2u32].private; + hash.sha3_256 r0 into r1 as field; + output r1 as field.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Verify the deployment transaction. It should fail because there are too many constraints. + assert!(vm.check_transaction(&deployment, None, rng).is_err()); + } + + #[test] + fn test_deployment_num_constant_overload() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_num_constants.aleo; +function do: + cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; + cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; + hash.bhp1024 r2 into r3 as u32; + output r3 as u32.private; +function do2: + cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; + cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; + hash.bhp1024 r2 into r3 as u32; + output r3 as u32.private;", + ) + .unwrap(); + + // Create the deployment transaction. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Verify the deployment transaction. It should fail because there are too many constants. + let check_tx_res = vm.check_transaction(&deployment, None, rng); + assert!(check_tx_res.is_err()); + } + + #[test] + fn test_deployment_synthesis_overreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_overreport.aleo; + +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(_, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Increase the number of constraints in the verifying keys. + let mut vks_with_overreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert)) in deployment.verifying_keys() { + let mut vk_deref = vk.deref().clone(); + vk_deref.circuit_info.num_constraints += 1; + let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); + vks_with_overreport.push((*id, (vk, cert.clone()))); + } + + // Each additional constraint costs 25 microcredits, so we need to increase the fee by 25 microcredits. + let required_fee = *fee.base_amount().unwrap() + 25; + // Authorize a new fee. + let fee_authorization = vm + .authorize_fee_public(&private_key, required_fee, 0, deployment.as_ref().to_deployment_id().unwrap(), rng) + .unwrap(); + // Compute the fee. + let fee = vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); + + // Create a new deployment transaction with the overreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); + let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); + + // Verify the deployment transaction. It should error when certificate checking for constraint count mismatch. + let res = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(res.is_err()); + } + + #[test] + fn test_deployment_synthesis_underreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_underreport.aleo; + +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Decrease the number of constraints in the verifying keys. + let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert)) in deployment.verifying_keys() { + let mut vk_deref = vk.deref().clone(); + vk_deref.circuit_info.num_constraints -= 2; + let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); + vks_with_underreport.push((*id, (vk, cert.clone()))); + } + + // Create a new deployment transaction with the underreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + + // Verify the deployment transaction. It should error when enforcing the first constraint over the vk limit. + let result = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(result.is_err()); + + // Create a standard transaction + // Prepare the inputs. + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + + // Check that the deployment transaction will be aborted if injected into a block. + let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); + + // Check that the block aborts the deployment transaction. + assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + } + + #[test] + fn test_deployment_variable_underreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_underreport.aleo; +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Decrease the number of reported variables in the verifying keys. + let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert)) in deployment.verifying_keys() { + let vk = VerifyingKey::new(Arc::new(vk.deref().clone()), vk.num_variables() - 2); + vks_with_underreport.push((*id, (vk.clone(), cert.clone()))); + } + + // Create a new deployment transaction with the underreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + + // Verify the deployment transaction. It should error when synthesizing the first variable over the vk limit. + let result = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(result.is_err()); + + // Create a standard transaction + // Prepare the inputs. + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + + // Check that the deployment transaction will be aborted if injected into a block. + let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); + + // Check that the block aborts the deployment transaction. + assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + } + + #[test] + #[ignore] + fn test_deployment_memory_overload() { + const NUM_DEPLOYMENTS: usize = 32; + + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize a view key. + let view_key = ViewKey::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program program_layer_0.aleo; + +mapping m: + key as u8.public; + value as u32.public; + +function do: + input r0 as u32.public; + async do r0 into r1; + output r1 as program_layer_0.aleo/do.future; + +finalize do: + input r0 as u32.public; + set r0 into m[0u8];", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // For each layer, deploy a program that calls the program from the previous layer. + for i in 1..NUM_DEPLOYMENTS { + let mut program_string = String::new(); + // Add the import statements. + for j in 0..i { + program_string.push_str(&format!("import program_layer_{}.aleo;\n", j)); + } + // Add the program body. + program_string.push_str(&format!( + "program program_layer_{i}.aleo; + +mapping m: + key as u8.public; + value as u32.public; + +function do: + input r0 as u32.public; + call program_layer_{prev}.aleo/do r0 into r1; + async do r0 r1 into r2; + output r2 as program_layer_{i}.aleo/do.future; + +finalize do: + input r0 as u32.public; + input r1 as program_layer_{prev}.aleo/do.future; + await r1; + set r0 into m[0u8];", + prev = i - 1 + )); + // Construct the program. + let program = Program::from_str(&program_string).unwrap(); + + // Deploy the program. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + } + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + + // Select a record to spend. + let record = Some(records.values().next().unwrap().decrypt(&view_key).unwrap()); + + // Prepare the inputs. + let inputs = [Value::::from_str("1u32").unwrap()].into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("program_layer_30.aleo", "do"), inputs, record, 0, None, rng).unwrap(); + + // Verify. + vm.check_transaction(&transaction, None, rng).unwrap(); + } + + #[test] + fn test_transfer_public_from_user() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Transfer credits from the caller to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_843_183, "Update me if the initial balance changes."); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); + } + + #[test] + fn test_transfer_public_as_signer_from_user() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Transfer credits from the caller to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public_as_signer"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_843_163, "Update me if the initial balance changes."); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); + } + + #[test] + fn transfer_public_from_program() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. + let program = Program::from_str( + r" +import credits.aleo; +program credits_wrapper.aleo; + +function transfer_public: + input r0 as address.public; + input r1 as u64.public; + call credits.aleo/transfer_public r0 r1 into r2; + async transfer_public r2 into r3; + output r3 as credits_wrapper.aleo/transfer_public.future; + +finalize transfer_public: + input r0 as credits.aleo/transfer_public.future; + await r0; + ", + ) + .unwrap(); + + // Get the address of the wrapper program. + let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); + let wrapper_program_address = wrapper_program_id.to_address().unwrap(); + + // Deploy the wrapper program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Transfer credits from the caller to the `credits_wrapper` program. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + [Value::from_str(&format!("{wrapper_program_address}")).unwrap(), Value::from_str("1u64").unwrap()] + .iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_996_914_808, "Update me if the initial balance changes."); + + // Check the balance of the `credits_wrapper` program. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); + + // Transfer credits from the `credits_wrapper` program to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits_wrapper.aleo", "transfer_public"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_996_862_283, "Update me if the initial balance changes."); + + // Check the balance of the `credits_wrapper` program. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 0); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); + } + + #[test] + fn transfer_public_as_signer_from_program() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. + let program = Program::from_str( + r" +import credits.aleo; +program credits_wrapper.aleo; + +function transfer_public_as_signer: + input r0 as address.public; + input r1 as u64.public; + call credits.aleo/transfer_public_as_signer r0 r1 into r2; + async transfer_public_as_signer r2 into r3; + output r3 as credits_wrapper.aleo/transfer_public_as_signer.future; + +finalize transfer_public_as_signer: + input r0 as credits.aleo/transfer_public_as_signer.future; + await r0; + ", + ) + .unwrap(); + + // Get the address of the wrapper program. + let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); + let wrapper_program_address = wrapper_program_id.to_address().unwrap(); + + // Deploy the wrapper program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Transfer credits from the signer using `credits_wrapper` program. + let transaction = vm + .execute( + &caller_private_key, + ("credits_wrapper.aleo", "transfer_public_as_signer"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_996_821_793, "Update me if the initial balance changes."); + + // Check the `credits_wrapper` program does not have any balance. + let balance = vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_address)), + ) + .unwrap(); + assert!(balance.is_none()); + + // Check the balance of the recipient. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 1, "Update me if the test amount changes."); + } + + #[test] + fn test_transfer_public_to_private_from_program() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize a recipient. + let recipient_private_key = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Check the balance of the caller. + let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); + let account_mapping_name = Identifier::from_str("account").unwrap(); + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); + + // Check that the recipient does not have a public balance. + let balance = vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap(); + assert!(balance.is_none()); + + // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public_as_signer` then `transfer_public_to_private`. + let program = Program::from_str( + r" +import credits.aleo; + +program credits_wrapper.aleo; + +function transfer_public_to_private: + input r0 as address.private; + input r1 as u64.public; + call credits.aleo/transfer_public_as_signer credits_wrapper.aleo r1 into r2; + call credits.aleo/transfer_public_to_private r0 r1 into r3 r4; + async transfer_public_to_private r2 r4 into r5; + output r3 as credits.aleo/credits.record; + output r5 as credits_wrapper.aleo/transfer_public_to_private.future; + +finalize transfer_public_to_private: + input r0 as credits.aleo/transfer_public_as_signer.future; + input r1 as credits.aleo/transfer_public_to_private.future; + contains credits.aleo/account[credits_wrapper.aleo] into r2; + assert.eq r2 false; + await r0; + get credits.aleo/account[credits_wrapper.aleo] into r3; + assert.eq r3 r0[2u32]; + await r1; + ", + ) + .unwrap(); + + // Get the address of the wrapper program. + let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); + + // Deploy the wrapper program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Call the wrapper program to transfer credits from the caller to the recipient. + let transaction = vm + .execute( + &caller_private_key, + ("credits_wrapper.aleo", "transfer_public_to_private"), + [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction.clone()], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Check the balance of the caller. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(caller_address)), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + + assert_eq!(balance, 182_499_996_071_881, "Update me if the initial balance changes."); + + // Check that the `credits_wrapper` program has a balance of 0. + let balance = match vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(wrapper_program_id.to_address().unwrap())), + ) + .unwrap() + { + Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, + _ => panic!("Expected a valid balance"), + }; + assert_eq!(balance, 0); + + // Check that the recipient does not have a public balance. + let balance = vm + .finalize_store() + .get_value_confirmed( + credits_program_id, + account_mapping_name, + &Plaintext::from(Literal::Address(recipient_address)), + ) + .unwrap(); + assert!(balance.is_none()); + + // Get the output record from the transaction and check that it is well-formed. + let records = transaction.records().collect_vec(); + assert_eq!(records.len(), 1); + let (commitment, record) = records[0]; + let record = record.decrypt(&ViewKey::try_from(&recipient_private_key).unwrap()).unwrap(); + assert_eq!(**record.owner(), recipient_address); + let data = record.data(); + assert_eq!(data.len(), 1); + match data.get(&Identifier::from_str("microcredits").unwrap()) { + Some(Entry::::Private(Plaintext::Literal(Literal::U64(value), _))) => { + assert_eq!(**value, 1) + } + _ => panic!("Incorrect record."), + } + + // Check that the record exists in the VM. + assert!(vm.transition_store().get_record(commitment).unwrap().is_some()); + + // Check that the serial number of the record does not exist in the VM. + assert!( + !vm.transition_store() + .contains_serial_number( + &Record::>::serial_number( + recipient_private_key, + *commitment + ) + .unwrap() + ) + .unwrap() + ); + } + + #[test] + fn test_large_transaction_is_aborted() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy a program that produces small transactions. + let program = small_transaction_program(); + + // Deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Deploy a program that produces large transactions. + let program = large_transaction_program(); + + // Deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); + + // Add the deployment to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Call the program to produce the small transaction. + let transaction = vm + .execute( + &caller_private_key, + ("testing_small.aleo", "small_transaction"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify the transaction. + vm.check_transaction(&transaction, None, rng).unwrap(); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Check that the transaction was accepted. + assert_eq!(block.transactions().num_accepted(), 1); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + + // Call the program to produce a large transaction. + let transaction = vm + .execute( + &caller_private_key, + ("testing_large.aleo", "large_transaction"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Verify that the transaction is invalid. + assert!(vm.check_transaction(&transaction, None, rng).is_err()); + + // Add the transaction to a block and update the VM. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + + // Check that the transaction was aborted. + assert_eq!(block.aborted_transaction_ids().len(), 1); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + } + + #[test] + fn test_vm_puzzle() { + // Attention: This test is used to ensure that the VM has performed downcasting correctly for + // the puzzle, and that the underlying traits in the puzzle are working correctly. Please + // *do not delete* this test as it is a critical safety check for the integrity of the + // instantiation of the puzzle in the VM. + + let rng = &mut TestRng::default(); + + // Initialize the VM. + let vm = sample_vm(); + + // Ensure this call succeeds. + vm.puzzle.prove(rng.gen(), rng.gen(), rng.gen(), None).unwrap(); + } + + #[cfg(feature = "rocks")] + #[test] + fn test_atomic_unpause_on_error() { + let rng = &mut TestRng::default(); + + // Initialize a genesis private key.. + let genesis_private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize a VM and sample 2 blocks using it. + let vm = sample_vm(); + vm.add_next_block(&genesis).unwrap(); + let block1 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); + vm.add_next_block(&block1).unwrap(); + let block2 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); + + // Create a new, rocks-based VM shadowing the 1st one. + let tempdir = tempfile::tempdir().unwrap(); + let vm = sample_vm_rocks(tempdir.path()); + vm.add_next_block(&genesis).unwrap(); + // This time, however, try to insert the 2nd block first, which fails due to height. + assert!(vm.add_next_block(&block2).is_err()); + + // It should still be possible to insert the 1st block afterwards. + vm.add_next_block(&block1).unwrap(); + } } diff --git a/synthesizer/src/vm/tests/mod.rs b/synthesizer/src/vm/tests/mod.rs index 5765584760..8b4b81a321 100644 --- a/synthesizer/src/vm/tests/mod.rs +++ b/synthesizer/src/vm/tests/mod.rs @@ -13,7 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod test_vm_standard; mod test_vm_update; use super::*; @@ -22,12 +21,6 @@ use crate::vm::test_helpers::*; use console::{ account::{Address, ViewKey}, - network::MainnetV0, - program::{Entry, Value}, + program::Value, }; -use ledger_block::Transition; -use ledger_test_helpers::{large_transaction_program, small_transaction_program}; use synthesizer_program::Program; - -use indexmap::IndexMap; -use synthesizer_snark::VerifyingKey; diff --git a/synthesizer/src/vm/tests/test_vm_standard.rs b/synthesizer/src/vm/tests/test_vm_standard.rs deleted file mode 100644 index 1724a06ddb..0000000000 --- a/synthesizer/src/vm/tests/test_vm_standard.rs +++ /dev/null @@ -1,1736 +0,0 @@ -// Copyright 2024 Aleo Network Foundation -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -#[test] -fn test_multiple_deployments_and_multiple_executions() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - - // Select a record to spend. - let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Split once. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "split"), - [Value::Record(record), Value::from_str("1000000000u64").unwrap()].iter(), // 1000 credits - None, - 0, - None, - rng, - ) - .unwrap(); - let records = transaction.records().collect_vec(); - let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); - let second_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - vm.add_next_block(&block).unwrap(); - - // Split again. - let mut transactions = Vec::new(); - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "split"), - [Value::Record(first_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits - None, - 0, - None, - rng, - ) - .unwrap(); - let records = transaction.records().collect_vec(); - let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); - let third_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); - transactions.push(transaction); - // Split again. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "split"), - [Value::Record(second_record), Value::from_str("100000000u64").unwrap()].iter(), // 100 credits - None, - 0, - None, - rng, - ) - .unwrap(); - let records = transaction.records().collect_vec(); - let second_record = records[0].1.clone().decrypt(&caller_view_key).unwrap(); - let fourth_record = records[1].1.clone().decrypt(&caller_view_key).unwrap(); - transactions.push(transaction); - // Add the split transactions to a block and update the VM. - let fee_block = sample_next_block(&vm, &caller_private_key, &transactions, rng).unwrap(); - vm.add_next_block(&fee_block).unwrap(); - - // Deploy the programs. - let first_program = r" -program test_program_1.aleo; -mapping map_0: - key as field.public; - value as field.public; -function init: - async init into r0; - output r0 as test_program_1.aleo/init.future; -finalize init: - set 0field into map_0[0field]; -function getter: - async getter into r0; - output r0 as test_program_1.aleo/getter.future; -finalize getter: - get map_0[0field] into r0; - "; - let second_program = r" -program test_program_2.aleo; -mapping map_0: - key as field.public; - value as field.public; -function init: - async init into r0; - output r0 as test_program_2.aleo/init.future; -finalize init: - set 0field into map_0[0field]; -function getter: - async getter into r0; - output r0 as test_program_2.aleo/getter.future; -finalize getter: - get map_0[0field] into r0; - "; - let first_deployment = vm - .deploy(&caller_private_key, &Program::from_str(first_program).unwrap(), Some(first_record), 1, None, rng) - .unwrap(); - let second_deployment = vm - .deploy(&caller_private_key, &Program::from_str(second_program).unwrap(), Some(second_record), 1, None, rng) - .unwrap(); - let deployment_block = - sample_next_block(&vm, &caller_private_key, &[first_deployment, second_deployment], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Execute the programs. - let first_execution = vm - .execute( - &caller_private_key, - ("test_program_1.aleo", "init"), - Vec::>::new().iter(), - Some(third_record), - 1, - None, - rng, - ) - .unwrap(); - let second_execution = vm - .execute( - &caller_private_key, - ("test_program_2.aleo", "init"), - Vec::>::new().iter(), - Some(fourth_record), - 1, - None, - rng, - ) - .unwrap(); - let execution_block = - sample_next_block(&vm, &caller_private_key, &[first_execution, second_execution], rng).unwrap(); - vm.add_next_block(&execution_block).unwrap(); -} - -#[test] -fn test_load_deployments_with_imports() { - // NOTE: This seed was chosen for the CI's RNG to ensure that the test passes. - let rng = &mut TestRng::fixed(123456789); - - // Initialize a new caller. - let caller_private_key = PrivateKey::::new(rng).unwrap(); - let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); - - // Initialize the VM. - let vm = sample_vm(); - // Initialize the genesis block. - let genesis = vm.genesis_beacon(&caller_private_key, rng).unwrap(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - let record_0 = records[0].1.decrypt(&caller_view_key).unwrap(); - let record_1 = records[1].1.decrypt(&caller_view_key).unwrap(); - let record_2 = records[2].1.decrypt(&caller_view_key).unwrap(); - let record_3 = records[3].1.decrypt(&caller_view_key).unwrap(); - - // Create the deployment for the first program. - let program_1 = r" -program first_program.aleo; - -function c: - input r0 as u8.private; - input r1 as u8.private; - add r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_1 = - vm.deploy(&caller_private_key, &Program::from_str(program_1).unwrap(), Some(record_0), 0, None, rng).unwrap(); - - // Deploy the first program. - let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_1.clone()], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Create the deployment for the second program. - let program_2 = r" -import first_program.aleo; - -program second_program.aleo; - -function b: - input r0 as u8.private; - input r1 as u8.private; - call first_program.aleo/c r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_2 = - vm.deploy(&caller_private_key, &Program::from_str(program_2).unwrap(), Some(record_1), 0, None, rng).unwrap(); - - // Deploy the second program. - let deployment_block = sample_next_block(&vm, &caller_private_key, &[deployment_2.clone()], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Create the deployment for the third program. - let program_3 = r" -import second_program.aleo; - -program third_program.aleo; - -function a: - input r0 as u8.private; - input r1 as u8.private; - call second_program.aleo/b r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_3 = - vm.deploy(&caller_private_key, &Program::from_str(program_3).unwrap(), Some(record_2), 0, None, rng).unwrap(); - - // Create the deployment for the fourth program. - let program_4 = r" -import second_program.aleo; -import first_program.aleo; - -program fourth_program.aleo; - -function a: - input r0 as u8.private; - input r1 as u8.private; - call second_program.aleo/b r0 r1 into r2; - output r2 as u8.private; - "; - let deployment_4 = - vm.deploy(&caller_private_key, &Program::from_str(program_4).unwrap(), Some(record_3), 0, None, rng).unwrap(); - - // Deploy the third and fourth program together. - let deployment_block = - sample_next_block(&vm, &caller_private_key, &[deployment_3.clone(), deployment_4.clone()], rng).unwrap(); - vm.add_next_block(&deployment_block).unwrap(); - - // Sanity check the ordering of the deployment transaction IDs from storage. - { - let deployment_transaction_ids = - vm.transaction_store().deployment_transaction_ids().map(|id| *id).collect::>(); - // This assert check is here to ensure that we are properly loading imports even though any order will work for `VM::from`. - // Note: `deployment_transaction_ids` is sorted lexicographically by transaction ID, so the order may change if we update internal methods. - assert_eq!( - deployment_transaction_ids, - vec![deployment_4.id(), deployment_3.id(), deployment_2.id(), deployment_1.id()], - "Update me if serialization has changed" - ); - } - - // Enforce that the VM can load properly with the imports. - assert!(VM::from(vm.store.clone()).is_ok()); -} - -#[test] -fn test_multiple_external_calls() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap(); - let address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - let record_0 = records.values().next().unwrap().decrypt(&caller_view_key).unwrap(); - let record_1 = records.values().nth(1).unwrap().decrypt(&caller_view_key).unwrap(); - let record_2 = records.values().nth(2).unwrap().decrypt(&caller_view_key).unwrap(); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the program. - let program = Program::from_str( - r" -import credits.aleo; - -program test_multiple_external_calls.aleo; - -function multitransfer: - input r0 as credits.aleo/credits.record; - input r1 as address.private; - input r2 as u64.private; - call credits.aleo/transfer_private r0 r1 r2 into r3 r4; - call credits.aleo/transfer_private r4 r1 r2 into r5 r6; - output r4 as credits.aleo/credits.record; - output r5 as credits.aleo/credits.record; - output r6 as credits.aleo/credits.record; - ", - ) - .unwrap(); - let deployment = vm.deploy(&caller_private_key, &program, Some(record_0), 1, None, rng).unwrap(); - vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap()).unwrap(); - - // Execute the programs. - let inputs = [ - Value::::Record(record_1), - Value::::from_str(&address.to_string()).unwrap(), - Value::::from_str("10u64").unwrap(), - ]; - let execution = vm - .execute( - &caller_private_key, - ("test_multiple_external_calls.aleo", "multitransfer"), - inputs.into_iter(), - Some(record_2), - 1, - None, - rng, - ) - .unwrap(); - vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[execution], rng).unwrap()).unwrap(); -} - -#[test] -fn test_nested_deployment_with_assert() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program child_program.aleo; - -function check: - input r0 as field.private; - assert.eq r0 123456789123456789123456789123456789123456789123456789field; - ", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Check that program is deployed. - assert!(vm.contains_program(&ProgramID::from_str("child_program.aleo").unwrap())); - - // Deploy the program that calls the program from the previous layer. - let program = Program::from_str( - r" -import child_program.aleo; - -program parent_program.aleo; - -function check: - input r0 as field.private; - call child_program.aleo/check r0; - ", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Check that program is deployed. - assert!(vm.contains_program(&ProgramID::from_str("parent_program.aleo").unwrap())); -} - -#[test] -fn test_deployment_with_external_records() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the program. - let program = Program::from_str( - r" -import credits.aleo; -program test_program.aleo; - -function transfer: - input r0 as credits.aleo/credits.record; - input r1 as u64.private; - input r2 as u64.private; - input r3 as [address; 10u32].private; - call credits.aleo/transfer_private r0 r3[0u32] r1 into r4 r5; - call credits.aleo/transfer_private r5 r3[0u32] r2 into r6 r7; -", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Check that program is deployed. - assert!(vm.contains_program(&ProgramID::from_str("test_program.aleo").unwrap())); -} - -#[test] -fn test_internal_fee_calls_are_invalid() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - let view_key = ViewKey::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).take(3).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - let record_0 = records.values().next().unwrap().decrypt(&view_key).unwrap(); - - // Deploy the program. - let program = Program::from_str( - r" -import credits.aleo; -program test_program.aleo; - -function call_fee_public: - input r0 as u64.private; - input r1 as u64.private; - input r2 as field.private; - call credits.aleo/fee_public r0 r1 r2 into r3; - async call_fee_public r3 into r4; - output r4 as test_program.aleo/call_fee_public.future; - -finalize call_fee_public: - input r0 as credits.aleo/fee_public.future; - await r0; - -function call_fee_private: - input r0 as credits.aleo/credits.record; - input r1 as u64.private; - input r2 as u64.private; - input r3 as field.private; - call credits.aleo/fee_private r0 r1 r2 r3 into r4; - output r4 as credits.aleo/credits.record; -", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - assert!(vm.check_transaction(&deployment, None, rng).is_ok()); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // Execute the programs. - let internal_base_fee_amount: u64 = rng.gen_range(1..1000); - let internal_priority_fee_amount: u64 = rng.gen_range(1..1000); - - // Ensure that the transaction that calls `fee_public` internally cannot be generated. - let inputs = [ - Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), - Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), - Value::::from_str("1field").unwrap(), - ]; - assert!( - vm.execute(&private_key, ("test_program.aleo", "call_fee_public"), inputs.into_iter(), None, 0, None, rng) - .is_err() - ); - - // Ensure that the transaction that calls `fee_private` internally cannot be generated. - let inputs = [ - Value::::Record(record_0), - Value::::from_str(&format!("{}u64", internal_base_fee_amount)).unwrap(), - Value::::from_str(&format!("{}u64", internal_priority_fee_amount)).unwrap(), - Value::::from_str("1field").unwrap(), - ]; - assert!( - vm.execute(&private_key, ("test_program.aleo", "call_fee_private"), inputs.into_iter(), None, 0, None, rng) - .is_err() - ); -} - -#[test] -#[ignore = "memory-intensive"] -fn test_deployment_synthesis_overload() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_overload.aleo; - -function do: - input r0 as [[u128; 32u32]; 2u32].private; - hash.sha3_256 r0 into r1 as field; - output r1 as field.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Verify the deployment transaction. It should fail because there are too many constraints. - assert!(vm.check_transaction(&deployment, None, rng).is_err()); -} - -#[test] -fn test_deployment_num_constant_overload() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_num_constants.aleo; -function do: - cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; - cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; - cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; - hash.bhp1024 r2 into r3 as u32; - output r3 as u32.private; -function do2: - cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; - cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; - cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; - hash.bhp1024 r2 into r3 as u32; - output r3 as u32.private;", - ) - .unwrap(); - - // Create the deployment transaction. - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Verify the deployment transaction. It should fail because there are too many constants. - let check_tx_res = vm.check_transaction(&deployment, None, rng); - assert!(check_tx_res.is_err()); -} - -#[test] -fn test_deployment_synthesis_overreport() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_overreport.aleo; - -function do: - input r0 as u32.private; - add r0 r0 into r1; - output r1 as u32.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Destructure the deployment transaction. - let Transaction::Deploy(_, program_owner, deployment, fee) = transaction else { - panic!("Expected a deployment transaction"); - }; - - // Increase the number of constraints in the verifying keys. - let mut vks_with_overreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { - let mut vk_deref = vk.deref().clone(); - vk_deref.circuit_info.num_constraints += 1; - let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); - vks_with_overreport.push((*id, (vk, cert.clone()))); - } - - // Each additional constraint costs 25 microcredits, so we need to increase the fee by 25 microcredits. - let required_fee = *fee.base_amount().unwrap() + 25; - // Authorize a new fee. - let fee_authorization = vm - .authorize_fee_public(&private_key, required_fee, 0, deployment.as_ref().to_deployment_id().unwrap(), rng) - .unwrap(); - // Compute the fee. - let fee = vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); - - // Create a new deployment transaction with the overreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); - let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); - - // Verify the deployment transaction. It should error when certificate checking for constraint count mismatch. - let res = vm.check_transaction(&adjusted_transaction, None, rng); - assert!(res.is_err()); -} - -#[test] -fn test_deployment_synthesis_underreport() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - let address = Address::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_underreport.aleo; - -function do: - input r0 as u32.private; - add r0 r0 into r1; - output r1 as u32.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Destructure the deployment transaction. - let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { - panic!("Expected a deployment transaction"); - }; - - // Decrease the number of constraints in the verifying keys. - let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { - let mut vk_deref = vk.deref().clone(); - vk_deref.circuit_info.num_constraints -= 2; - let vk = VerifyingKey::new(Arc::new(vk_deref), vk.num_variables()); - vks_with_underreport.push((*id, (vk, cert.clone()))); - } - - // Create a new deployment transaction with the underreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); - let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); - - // Verify the deployment transaction. It should error when enforcing the first constraint over the vk limit. - let result = vm.check_transaction(&adjusted_transaction, None, rng); - assert!(result.is_err()); - - // Create a standard transaction - // Prepare the inputs. - let inputs = [ - Value::::from_str(&address.to_string()).unwrap(), - Value::::from_str("1u64").unwrap(), - ] - .into_iter(); - - // Execute. - let transaction = - vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); - - // Check that the deployment transaction will be aborted if injected into a block. - let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); - - // Check that the block aborts the deployment transaction. - assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); - - // Update the VM. - vm.add_next_block(&block).unwrap(); -} - -#[test] -fn test_deployment_variable_underreport() { - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - let address = Address::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program synthesis_underreport.aleo; -function do: - input r0 as u32.private; - add r0 r0 into r1; - output r1 as u32.public;", - ) - .unwrap(); - - // Create the deployment transaction. - let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - // Destructure the deployment transaction. - let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { - panic!("Expected a deployment transaction"); - }; - - // Decrease the number of reported variables in the verifying keys. - let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { - let vk = VerifyingKey::new(Arc::new(vk.deref().clone()), vk.num_variables() - 2); - vks_with_underreport.push((*id, (vk.clone(), cert.clone()))); - } - - // Create a new deployment transaction with the underreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); - let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); - - // Verify the deployment transaction. It should error when synthesizing the first variable over the vk limit. - let result = vm.check_transaction(&adjusted_transaction, None, rng); - assert!(result.is_err()); - - // Create a standard transaction - // Prepare the inputs. - let inputs = [ - Value::::from_str(&address.to_string()).unwrap(), - Value::::from_str("1u64").unwrap(), - ] - .into_iter(); - - // Execute. - let transaction = - vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); - - // Check that the deployment transaction will be aborted if injected into a block. - let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); - - // Check that the block aborts the deployment transaction. - assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); - - // Update the VM. - vm.add_next_block(&block).unwrap(); -} - -#[test] -#[ignore] -fn test_deployment_memory_overload() { - const NUM_DEPLOYMENTS: usize = 32; - - let rng = &mut TestRng::default(); - - // Initialize a private key. - let private_key = sample_genesis_private_key(rng); - - // Initialize a view key. - let view_key = ViewKey::try_from(&private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy the base program. - let program = Program::from_str( - r" -program program_layer_0.aleo; - -mapping m: - key as u8.public; - value as u32.public; - -function do: - input r0 as u32.public; - async do r0 into r1; - output r1 as program_layer_0.aleo/do.future; - -finalize do: - input r0 as u32.public; - set r0 into m[0u8];", - ) - .unwrap(); - - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - - // For each layer, deploy a program that calls the program from the previous layer. - for i in 1..NUM_DEPLOYMENTS { - let mut program_string = String::new(); - // Add the import statements. - for j in 0..i { - program_string.push_str(&format!("import program_layer_{}.aleo;\n", j)); - } - // Add the program body. - program_string.push_str(&format!( - "program program_layer_{i}.aleo; - -mapping m: - key as u8.public; - value as u32.public; - -function do: - input r0 as u32.public; - call program_layer_{prev}.aleo/do r0 into r1; - async do r0 r1 into r2; - output r2 as program_layer_{i}.aleo/do.future; - -finalize do: - input r0 as u32.public; - input r1 as program_layer_{prev}.aleo/do.future; - await r1; - set r0 into m[0u8];", - prev = i - 1 - )); - // Construct the program. - let program = Program::from_str(&program_string).unwrap(); - - // Deploy the program. - let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); - - vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); - } - - // Fetch the unspent records. - let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); - trace!("Unspent Records:\n{:#?}", records); - - // Select a record to spend. - let record = Some(records.values().next().unwrap().decrypt(&view_key).unwrap()); - - // Prepare the inputs. - let inputs = [Value::::from_str("1u32").unwrap()].into_iter(); - - // Execute. - let transaction = vm.execute(&private_key, ("program_layer_30.aleo", "do"), inputs, record, 0, None, rng).unwrap(); - - // Verify. - vm.check_transaction(&transaction, None, rng).unwrap(); -} - -#[test] -fn test_transfer_public_from_user() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Transfer credits from the caller to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "transfer_public"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_843_183, "Update me if the initial balance changes."); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); -} - -#[test] -fn test_transfer_public_as_signer_from_user() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Transfer credits from the caller to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "transfer_public_as_signer"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_843_163, "Update me if the initial balance changes."); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); -} - -#[test] -fn transfer_public_from_program() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. - let program = Program::from_str( - r" -import credits.aleo; -program credits_wrapper.aleo; - -function transfer_public: - input r0 as address.public; - input r1 as u64.public; - call credits.aleo/transfer_public r0 r1 into r2; - async transfer_public r2 into r3; - output r3 as credits_wrapper.aleo/transfer_public.future; - -finalize transfer_public: - input r0 as credits.aleo/transfer_public.future; - await r0; - ", - ) - .unwrap(); - - // Get the address of the wrapper program. - let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); - let wrapper_program_address = wrapper_program_id.to_address().unwrap(); - - // Deploy the wrapper program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Transfer credits from the caller to the `credits_wrapper` program. - let transaction = vm - .execute( - &caller_private_key, - ("credits.aleo", "transfer_public"), - [Value::from_str(&format!("{wrapper_program_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_996_914_808, "Update me if the initial balance changes."); - - // Check the balance of the `credits_wrapper` program. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); - - // Transfer credits from the `credits_wrapper` program to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits_wrapper.aleo", "transfer_public"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_996_862_283, "Update me if the initial balance changes."); - - // Check the balance of the `credits_wrapper` program. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 0); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); -} - -#[test] -fn transfer_public_as_signer_from_program() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public`. - let program = Program::from_str( - r" -import credits.aleo; -program credits_wrapper.aleo; - -function transfer_public_as_signer: - input r0 as address.public; - input r1 as u64.public; - call credits.aleo/transfer_public_as_signer r0 r1 into r2; - async transfer_public_as_signer r2 into r3; - output r3 as credits_wrapper.aleo/transfer_public_as_signer.future; - -finalize transfer_public_as_signer: - input r0 as credits.aleo/transfer_public_as_signer.future; - await r0; - ", - ) - .unwrap(); - - // Get the address of the wrapper program. - let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); - let wrapper_program_address = wrapper_program_id.to_address().unwrap(); - - // Deploy the wrapper program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Transfer credits from the signer using `credits_wrapper` program. - let transaction = vm - .execute( - &caller_private_key, - ("credits_wrapper.aleo", "transfer_public_as_signer"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_996_821_793, "Update me if the initial balance changes."); - - // Check the `credits_wrapper` program does not have any balance. - let balance = vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_address)), - ) - .unwrap(); - assert!(balance.is_none()); - - // Check the balance of the recipient. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 1, "Update me if the test amount changes."); -} - -#[test] -fn test_transfer_public_to_private_from_program() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - let caller_address = Address::try_from(&caller_private_key).unwrap(); - - // Initialize a recipient. - let recipient_private_key = PrivateKey::new(rng).unwrap(); - let recipient_address = Address::try_from(&recipient_private_key).unwrap(); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Check the balance of the caller. - let credits_program_id = ProgramID::from_str("credits.aleo").unwrap(); - let account_mapping_name = Identifier::from_str("account").unwrap(); - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 182_499_999_894_244, "Update me if the initial balance changes."); - - // Check that the recipient does not have a public balance. - let balance = vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap(); - assert!(balance.is_none()); - - // Initialize a wrapper program, importing `credits.aleo` and calling `transfer_public_as_signer` then `transfer_public_to_private`. - let program = Program::from_str( - r" -import credits.aleo; - -program credits_wrapper.aleo; - -function transfer_public_to_private: - input r0 as address.private; - input r1 as u64.public; - call credits.aleo/transfer_public_as_signer credits_wrapper.aleo r1 into r2; - call credits.aleo/transfer_public_to_private r0 r1 into r3 r4; - async transfer_public_to_private r2 r4 into r5; - output r3 as credits.aleo/credits.record; - output r5 as credits_wrapper.aleo/transfer_public_to_private.future; - -finalize transfer_public_to_private: - input r0 as credits.aleo/transfer_public_as_signer.future; - input r1 as credits.aleo/transfer_public_to_private.future; - contains credits.aleo/account[credits_wrapper.aleo] into r2; - assert.eq r2 false; - await r0; - get credits.aleo/account[credits_wrapper.aleo] into r3; - assert.eq r3 r0[2u32]; - await r1; - ", - ) - .unwrap(); - - // Get the address of the wrapper program. - let wrapper_program_id = ProgramID::from_str("credits_wrapper.aleo").unwrap(); - - // Deploy the wrapper program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Call the wrapper program to transfer credits from the caller to the recipient. - let transaction = vm - .execute( - &caller_private_key, - ("credits_wrapper.aleo", "transfer_public_to_private"), - [Value::from_str(&format!("{recipient_address}")).unwrap(), Value::from_str("1u64").unwrap()].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction.clone()], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Check the balance of the caller. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(caller_address)), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - - assert_eq!(balance, 182_499_996_071_881, "Update me if the initial balance changes."); - - // Check that the `credits_wrapper` program has a balance of 0. - let balance = match vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(wrapper_program_id.to_address().unwrap())), - ) - .unwrap() - { - Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, - _ => panic!("Expected a valid balance"), - }; - assert_eq!(balance, 0); - - // Check that the recipient does not have a public balance. - let balance = vm - .finalize_store() - .get_value_confirmed( - credits_program_id, - account_mapping_name, - &Plaintext::from(Literal::Address(recipient_address)), - ) - .unwrap(); - assert!(balance.is_none()); - - // Get the output record from the transaction and check that it is well-formed. - let records = transaction.records().collect_vec(); - assert_eq!(records.len(), 1); - let (commitment, record) = records[0]; - let record = record.decrypt(&ViewKey::try_from(&recipient_private_key).unwrap()).unwrap(); - assert_eq!(**record.owner(), recipient_address); - let data = record.data(); - assert_eq!(data.len(), 1); - match data.get(&Identifier::from_str("microcredits").unwrap()) { - Some(Entry::::Private(Plaintext::Literal(Literal::U64(value), _))) => { - assert_eq!(**value, 1) - } - _ => panic!("Incorrect record."), - } - - // Check that the record exists in the VM. - assert!(vm.transition_store().get_record(commitment).unwrap().is_some()); - - // Check that the serial number of the record does not exist in the VM. - assert!( - !vm.transition_store() - .contains_serial_number( - &Record::>::serial_number(recipient_private_key, *commitment) - .unwrap() - ) - .unwrap() - ); -} - -#[test] -fn test_large_transaction_is_aborted() { - let rng = &mut TestRng::default(); - - // Initialize a new caller. - let caller_private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize the VM. - let vm = sample_vm(); - - // Update the VM. - vm.add_next_block(&genesis).unwrap(); - - // Deploy a program that produces small transactions. - let program = small_transaction_program(); - - // Deploy the program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Deploy a program that produces large transactions. - let program = large_transaction_program(); - - // Deploy the program. - let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng).unwrap(); - - // Add the deployment to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Call the program to produce the small transaction. - let transaction = vm - .execute( - &caller_private_key, - ("testing_small.aleo", "small_transaction"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify the transaction. - vm.check_transaction(&transaction, None, rng).unwrap(); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Check that the transaction was accepted. - assert_eq!(block.transactions().num_accepted(), 1); - - // Update the VM. - vm.add_next_block(&block).unwrap(); - - // Call the program to produce a large transaction. - let transaction = vm - .execute( - &caller_private_key, - ("testing_large.aleo", "large_transaction"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - // Verify that the transaction is invalid. - assert!(vm.check_transaction(&transaction, None, rng).is_err()); - - // Add the transaction to a block and update the VM. - let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); - - // Check that the transaction was aborted. - assert_eq!(block.aborted_transaction_ids().len(), 1); - - // Update the VM. - vm.add_next_block(&block).unwrap(); -} - -#[test] -fn test_vm_puzzle() { - // Attention: This test is used to ensure that the VM has performed downcasting correctly for - // the puzzle, and that the underlying traits in the puzzle are working correctly. Please - // *do not delete* this test as it is a critical safety check for the integrity of the - // instantiation of the puzzle in the VM. - - let rng = &mut TestRng::default(); - - // Initialize the VM. - let vm = sample_vm(); - - // Ensure this call succeeds. - vm.puzzle.prove(rng.gen(), rng.gen(), rng.gen(), None).unwrap(); -} - -#[cfg(feature = "rocks")] -#[test] -fn test_atomic_unpause_on_error() { - let rng = &mut TestRng::default(); - - // Initialize a genesis private key.. - let genesis_private_key = sample_genesis_private_key(rng); - - // Initialize the genesis block. - let genesis = sample_genesis_block(rng); - - // Initialize a VM and sample 2 blocks using it. - let vm = sample_vm(); - vm.add_next_block(&genesis).unwrap(); - let block1 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); - vm.add_next_block(&block1).unwrap(); - let block2 = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); - - // Create a new, rocks-based VM shadowing the 1st one. - let tempdir = tempfile::tempdir().unwrap(); - let vm = sample_vm_rocks(tempdir.path()); - vm.add_next_block(&genesis).unwrap(); - // This time, however, try to insert the 2nd block first, which fails due to height. - assert!(vm.add_next_block(&block2).is_err()); - - // It should still be possible to insert the 1st block afterwards. - vm.add_next_block(&block1).unwrap(); -} diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index f78416748b..9d4fc73841 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -479,7 +479,7 @@ impl> VM { mod tests { use super::*; - use crate::vm::test_helpers::sample_finalize_state; + use crate::vm::test_helpers::{sample_finalize_state, sample_next_block}; use console::{ account::{Address, ViewKey}, types::Field, @@ -757,13 +757,28 @@ mod tests { #[test] fn test_failed_credits_deployment() { let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. let vm = crate::vm::test_helpers::sample_vm(); + vm.add_next_block(&genesis).unwrap(); // Fetch the credits program let program = Program::credits().unwrap(); - // Ensure that the program can't be deployed. - assert!(vm.deploy_raw(&program, rng).is_err()); + // Ensure that the program can't be deployed successfully. + // In this case the deployment can be successfully generated as it is a valid update to `credits.aleo`. + // However, when the transaction fails to verify and is not accepted when added to the block. + let deployment = vm.deploy(&caller_private_key, &program, None, 0u64, None, rng).unwrap(); + assert!(vm.check_transaction(&deployment, None, rng).is_err()); + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 0); + vm.add_next_block(&block).unwrap(); // Create a new `credits.aleo` program. let program = Program::from_str( @@ -781,8 +796,9 @@ function compute: ) .unwrap(); - // Ensure that the program can't be deployed. - assert!(vm.deploy_raw(&program, rng).is_err()); + // Ensure that the program can't be deployed successfully. + // In this case, the deployment cannot be successfully generated as it is an invalid update to `credits.aleo`. + assert!(vm.deploy(&caller_private_key, &program, None, 0u64, None, rng).is_err()); } #[test] From 0d8e040f36bcaf95da14a87b12676895de19a38b Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 14 Feb 2025 08:39:08 -0800 Subject: [PATCH 40/46] Fix and move unrelated test to different branch --- console/network/src/lib.rs | 1 + ledger/src/tests.rs | 261 ------------------ synthesizer/process/src/finalize.rs | 6 +- .../read_external_mapping.out | 2 +- 4 files changed, 5 insertions(+), 265 deletions(-) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index c366bea139..10c778d3b0 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -190,6 +190,7 @@ pub trait Network: const MAX_OUTPUTS: usize = 16; /// The maximum program depth. + /// Note. This is unused. const MAX_PROGRAM_DEPTH: usize = 64; /// The maximum number of imports. const MAX_IMPORTS: usize = 64; diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 800d8ec028..d3aacbd33c 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -3142,264 +3142,3 @@ fn test_forged_block_subdags() { assert!(ledger.check_next_block(&forged_block_2_from_both_subdags, &mut rand::thread_rng()).is_err()); } } - -#[test] -fn test_record_creation_and_consumption_in_call() { - let rng = &mut TestRng::default(); - - // Sample the test environment. - let crate::test_helpers::TestEnv { ledger, private_key, view_key, .. } = crate::test_helpers::sample_test_env(rng); - - // A helper function to get the record counts. - let get_record_counts = || { - let slow_spent_filter = RecordsFilter::SlowSpent(private_key); - let slow_unspent_filter = RecordsFilter::SlowUnspent(private_key); - let spent_records = ledger.find_records(&view_key, RecordsFilter::Spent).unwrap().collect_vec().len(); - let slow_spent_records = ledger.find_records(&view_key, slow_spent_filter).unwrap().collect_vec().len(); - let unspent_records = ledger.find_records(&view_key, RecordsFilter::Unspent).unwrap().collect_vec().len(); - let slow_unspent_records = ledger.find_records(&view_key, slow_unspent_filter).unwrap().collect_vec().len(); - let records = ledger.records().collect_vec().len(); - (spent_records, slow_spent_records, unspent_records, slow_unspent_records, records) - }; - - // Check the initial record counts. - let ( - initial_spent_records, - initial_slow_spent_records, - initial_unspent_records, - initial_slow_unspent_records, - initial_records, - ) = get_record_counts(); - assert_eq!(0, initial_spent_records); - assert_eq!(0, initial_slow_spent_records); - assert_eq!(4, initial_unspent_records); - assert_eq!(4, initial_slow_unspent_records); - assert_eq!(4, initial_records); - - // Initialize the two programs. - let program_0 = Program::from_str( - r" -program child.aleo; - -record data: - owner as address.private; - val as u64.private; - -function mint: - cast self.signer 0u64 into r0 as data.record; - output r0 as data.record; - -function burn: - input r0 as data.record; - ", - ) - .unwrap(); - - let program_1 = Program::from_str( - r" -import child.aleo; - -program parent.aleo; - -function create_without_output: - call child.aleo/mint into r0; - -function create: - call child.aleo/mint into r0; - output r0 as child.aleo/data.record; - -function consume_without_call: - input r0 as child.aleo/data.record; - -function consume: - input r0 as child.aleo/data.record; - call child.aleo/burn r0; - -function create_and_consume: - call child.aleo/mint into r0; - call child.aleo/burn r0; - ", - ) - .unwrap(); - - // Deploy the programs. - let deployment_0 = ledger.vm().deploy(&private_key, &program_0, None, 0, None, rng).unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_0], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - let deployment_1 = ledger.vm().deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_1], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Call the `mint` function. - let transaction = ledger - .vm() - .execute(&private_key, ("child.aleo", "mint"), Vec::>::new().iter(), None, 0, None, rng) - .unwrap(); - let mint_record = transaction.records().last().unwrap().1.decrypt(&view_key).unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Check the record counts. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = - get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records); - assert_eq!(num_slow_spent_records, initial_slow_spent_records); - assert_eq!(num_unspent_records, initial_unspent_records + 1); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); - assert_eq!(num_records, initial_records + 1); - - // Call the `create_without_output` function. - let transaction = ledger - .vm() - .execute( - &private_key, - ("parent.aleo", "create_without_output"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Check the record counts. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = - get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records); - assert_eq!(num_slow_spent_records, initial_slow_spent_records); - assert_eq!(num_unspent_records, initial_unspent_records + 2); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); - assert_eq!(num_records, initial_records + 2); - - // Call the `burn` function on record created by `create_without_output`. - let record = block.records().collect_vec().last().unwrap().1.decrypt(&view_key).unwrap(); - let transaction = ledger - .vm() - .execute(&private_key, ("child.aleo", "burn"), vec![Value::Record(record)].iter(), None, 0, None, rng) - .unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Check the record counts. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = - get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records + 1); - assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); - assert_eq!(num_unspent_records, initial_unspent_records + 1); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); - assert_eq!(num_records, initial_records + 2); - - // Call the `create` function. - let transaction = ledger - .vm() - .execute( - &private_key, - ("parent.aleo", "create"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Ensure that a record was created and spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = - get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records + 1); - assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); - assert_eq!(num_unspent_records, initial_unspent_records + 2); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); - assert_eq!(num_records, initial_records + 3); - - // Call the `consume_without_call` function. - let transaction = ledger - .vm() - .execute( - &private_key, - ("parent.aleo", "consume_without_call"), - vec![Value::Record(mint_record.clone())].iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Ensure that no records were created or spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = - get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records + 1); - assert_eq!(num_slow_spent_records, initial_slow_spent_records + 1); - assert_eq!(num_unspent_records, initial_unspent_records + 2); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 2); - assert_eq!(num_records, initial_records + 3); - - // Call the `consume` function. - let transaction = ledger - .vm() - .execute(&private_key, ("parent.aleo", "consume"), vec![Value::Record(mint_record)].iter(), None, 0, None, rng) - .unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Ensure that the record was spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = - get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records + 2); - assert_eq!(num_slow_spent_records, initial_slow_spent_records + 2); - assert_eq!(num_unspent_records, initial_unspent_records + 1); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); - assert_eq!(num_records, initial_records + 3); - - // Call the `create_and_consume` function. - let transaction = ledger - .vm() - .execute( - &private_key, - ("parent.aleo", "create_and_consume"), - Vec::>::new().iter(), - None, - 0, - None, - rng, - ) - .unwrap(); - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); - assert_eq!(block.transactions().num_accepted(), 1); - ledger.advance_to_next_block(&block).unwrap(); - - // Ensure that a record was created and spent. - let (num_spent_records, num_slow_spent_records, num_unspent_records, num_slow_unspent_records, num_records) = - get_record_counts(); - assert_eq!(num_spent_records, initial_spent_records + 3); - assert_eq!(num_slow_spent_records, initial_slow_spent_records + 3); - assert_eq!(num_unspent_records, initial_unspent_records + 1); - assert_eq!(num_slow_unspent_records, initial_slow_unspent_records + 1); - assert_eq!(num_records, initial_records + 4); -} diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 5eb962f16b..580b36ac86 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -258,12 +258,12 @@ fn finalize_transition>( states.pop() { // Get the finalize logic. - let finalize = match stack.get_function_ref(future.function_name())?.finalize_logic() { + let finalize = match stack.get_function_ref(registers.function_name())?.finalize_logic() { Some(finalize) => finalize, None => bail!( "The function '{}/{}' does not have an associated finalize block", - future.program_id(), - future.function_name() + stack.program_id(), + registers.function_name() ), }; // Evaluate the commands. diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out b/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out index 4b81518d24..ae649857b7 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out @@ -92,4 +92,4 @@ additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"8401929608727184541382889469312440652682460087199782119730570970943355959220field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 12331u64\n ]\n}"}' + - '{"type":"future","id":"4369745470310636653607709784601243833431868722463490242434780063887791579010field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3831u64\n ]\n}"}' From 0ea9bc403e9d465b56b8e761c7e91cd87279f46a Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:57:50 -0800 Subject: [PATCH 41/46] Add debugs --- .circleci/config.yml | 1 + ledger/narwhal/data/src/lib.rs | 35 ++++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f55a916ec5..5014efd611 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -580,6 +580,7 @@ jobs: resource_class: << pipeline.parameters.small >> steps: - run_serial: + flags: -- --nocapture workspace_member: ledger/narwhal/data cache_key: v1.0.0-rust-1.81.0-snarkvm-ledger-narwhal-data-cache diff --git a/ledger/narwhal/data/src/lib.rs b/ledger/narwhal/data/src/lib.rs index f45a782f25..a34a62e828 100644 --- a/ledger/narwhal/data/src/lib.rs +++ b/ledger/narwhal/data/src/lib.rs @@ -251,23 +251,46 @@ mod tests { // Sample transactions let transactions = [ - ledger_test_helpers::sample_deployment_transaction(true, rng), - ledger_test_helpers::sample_deployment_transaction(false, rng), - ledger_test_helpers::sample_execution_transaction_with_fee(true, rng), - ledger_test_helpers::sample_execution_transaction_with_fee(false, rng), - ledger_test_helpers::sample_fee_private_transaction(rng), - ledger_test_helpers::sample_fee_public_transaction(rng), + { + println!("Sampling 0"); + ledger_test_helpers::sample_deployment_transaction(true, rng) + }, + { + println!("Sampling 1"); + ledger_test_helpers::sample_deployment_transaction(false, rng) + }, + { + println!("Sampling 2"); + ledger_test_helpers::sample_execution_transaction_with_fee(true, rng) + }, + { + println!("Sampling 3"); + ledger_test_helpers::sample_execution_transaction_with_fee(false, rng) + }, + { + println!("Sampling 4"); + ledger_test_helpers::sample_fee_private_transaction(rng) + }, + { + println!("Sampling 5"); + ledger_test_helpers::sample_fee_public_transaction(rng) + }, ]; for transaction in transactions.into_iter() { + println!("START"); // Convert the transaction to a Data buffer. let data_bytes: Data> = Data::Buffer(transaction.to_bytes_le().unwrap().into()); + println!("0"); // Convert the transaction to a data object. let data = Data::Object(transaction); // Compute the checksums. + println!("1"); let checksum_1 = data_bytes.to_checksum::().unwrap(); + println!("2"); let checksum_2 = data.to_checksum::().unwrap(); + println!("END"); // Ensure the checksums are equal. assert_eq!(checksum_1, checksum_2); From 415c6208c3816a9af1b447c725aa535966acc51a Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:02:03 -0800 Subject: [PATCH 42/46] Use weak references back to global stacks --- .../process/src/stack/helpers/initialize.rs | 2 +- synthesizer/process/src/stack/mod.rs | 15 +++-- synthesizer/process/src/tests/test_execute.rs | 6 +- synthesizer/src/vm/mod.rs | 56 +++++++++++++++++++ 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 598030acc1..0e883af416 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -25,7 +25,7 @@ impl Stack { // Construct the stack for the program. let mut stack = Self { program: program.clone(), - stacks: process.stacks.clone(), + stacks: Arc::downgrade(&process.stacks), register_types: Default::default(), finalize_types: Default::default(), universal_srs: process.universal_srs().clone(), diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 9f78327a7c..59e98f9ea8 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -71,7 +71,7 @@ use synthesizer_snark::{Certificate, ProvingKey, UniversalSRS, VerifyingKey}; use aleo_std::prelude::{finish, lap, timer}; use indexmap::IndexMap; use parking_lot::RwLock; -use std::sync::Arc; +use std::sync::{Arc, Weak}; #[cfg(not(feature = "serial"))] use rayon::prelude::*; @@ -185,7 +185,7 @@ pub struct Stack { /// The program (record types, structs, functions). program: Program, /// A reference to the global stack map. - stacks: Arc, Arc>>>>, + stacks: Weak, Arc>>>>, /// The mapping of closure and function names to their register types. register_types: IndexMap, RegisterTypes>, /// The mapping of finalize names to their register types. @@ -269,12 +269,17 @@ impl StackProgram for Stack { fn get_external_stack(&self, program_id: &ProgramID) -> Result>> { // Check that the program ID is imported by the program. ensure!(self.program.contains_import(program_id), "External program '{program_id}' is not imported."); - // Retrieve the stack. - self.stacks + // Upgrade the weak reference to the process-level stack map and retrieve the external stack. + let result = self + .stacks + .upgrade() + .ok_or_else(|| anyhow!("Process-level stack map does not exist"))? .read() .get(program_id) .cloned() - .ok_or_else(|| anyhow!("External program '{program_id}' does not exist.")) + .ok_or_else(|| anyhow!("External stack for '{program_id}' does not exist")); + // Return the external stack. + result } /// Returns the function with the given function name. diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index bac91dcbf5..b55f343df2 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -2640,11 +2640,11 @@ fn test_program_exceeding_transaction_spend_limit() { // Initialize a `Process`. let mut process = Process::::load().unwrap(); - // Attempt to add the program to the process, which should fail. + // Attempt to add the program to the process should pass, as the check happens during finalization. let result = process.add_program(&program); assert!(result.is_err()); - // Attempt to initialize a `Stack` directly with the program, which should fail. + // Initialize a `Stack` directly with the program should pass, as the check happens during finalization. let result = Stack::initialize(&process, &program, 0); - assert!(result.is_err()); + assert!(result.is_ok()); } diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 14e5d94617..b2f96f3e71 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -2579,4 +2579,60 @@ finalize transfer_public_to_private: // It should still be possible to insert the 1st block afterwards. vm.add_next_block(&block1).unwrap(); } + + #[test] + fn test_program_exceeding_transaction_spend_limit() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + vm.add_next_block(&genesis).unwrap(); + + // Construct a finalize body whose finalize cost is excessively large. + let mut finalize_body = r" + cast 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 into r0 as [u8; 16u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u8; 16u32]; 16u32]; + cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[[u8; 16u32]; 16u32]; 16u32];" + .to_string(); + (3..500).for_each(|i| { + finalize_body.push_str(&format!("hash.bhp256 r2 into r{i} as field;\n")); + }); + // Construct the program. + let program = Program::from_str(&format!( + r"program test_max_spend_limit.aleo; + function foo: + async foo into r0; + output r0 as test_max_spend_limit.aleo/foo.future; + finalize foo:{finalize_body}", + )) + .unwrap(); + + // Deploy the program. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &private_key, &[deployment], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + vm.add_next_block(&block).unwrap(); + + // Execute the program. + let transaction = vm + .execute( + &private_key, + ("test_max_spend_limit.aleo", "foo"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &private_key, &[transaction], rng).unwrap(); + assert_eq!(block.transactions().num_rejected(), 1); + vm.add_next_block(&block).unwrap(); + } } From c6375638b7b697687dd294264f9555afd11c9d62 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:17:18 -0800 Subject: [PATCH 43/46] Adjust test --- synthesizer/process/src/stack/helpers/initialize.rs | 2 -- synthesizer/process/src/tests/test_execute.rs | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 0e883af416..849e053cc6 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -45,8 +45,6 @@ impl Stack { } } - println!("ONE"); - // Add the program closures to the stack. for closure in program.closures().values() { // Add the closure to the stack. diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index b55f343df2..2b653bc0d9 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -2529,7 +2529,9 @@ fn test_long_import_chain() { )) .unwrap(); let result = process.add_program(&program); - assert!(result.is_err()); + // Programs may create long import chains as long as number of calls does not exceed the maximum number of transitions. + // TODO (@d0cd) Determine whether that this is okay + assert!(result.is_ok()); } #[test] From b3ada7637690c01194db284c95bc6227ce19708a Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:37:11 -0800 Subject: [PATCH 44/46] Cleanup --- .circleci/config.yml | 1 - ledger/narwhal/data/src/lib.rs | 35 ++++--------------- synthesizer/process/src/finalize.rs | 5 --- synthesizer/process/src/stack/mod.rs | 1 - synthesizer/process/src/tests/test_execute.rs | 2 +- synthesizer/src/vm/finalize.rs | 4 --- 6 files changed, 7 insertions(+), 41 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5014efd611..f55a916ec5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -580,7 +580,6 @@ jobs: resource_class: << pipeline.parameters.small >> steps: - run_serial: - flags: -- --nocapture workspace_member: ledger/narwhal/data cache_key: v1.0.0-rust-1.81.0-snarkvm-ledger-narwhal-data-cache diff --git a/ledger/narwhal/data/src/lib.rs b/ledger/narwhal/data/src/lib.rs index a34a62e828..f45a782f25 100644 --- a/ledger/narwhal/data/src/lib.rs +++ b/ledger/narwhal/data/src/lib.rs @@ -251,46 +251,23 @@ mod tests { // Sample transactions let transactions = [ - { - println!("Sampling 0"); - ledger_test_helpers::sample_deployment_transaction(true, rng) - }, - { - println!("Sampling 1"); - ledger_test_helpers::sample_deployment_transaction(false, rng) - }, - { - println!("Sampling 2"); - ledger_test_helpers::sample_execution_transaction_with_fee(true, rng) - }, - { - println!("Sampling 3"); - ledger_test_helpers::sample_execution_transaction_with_fee(false, rng) - }, - { - println!("Sampling 4"); - ledger_test_helpers::sample_fee_private_transaction(rng) - }, - { - println!("Sampling 5"); - ledger_test_helpers::sample_fee_public_transaction(rng) - }, + ledger_test_helpers::sample_deployment_transaction(true, rng), + ledger_test_helpers::sample_deployment_transaction(false, rng), + ledger_test_helpers::sample_execution_transaction_with_fee(true, rng), + ledger_test_helpers::sample_execution_transaction_with_fee(false, rng), + ledger_test_helpers::sample_fee_private_transaction(rng), + ledger_test_helpers::sample_fee_public_transaction(rng), ]; for transaction in transactions.into_iter() { - println!("START"); // Convert the transaction to a Data buffer. let data_bytes: Data> = Data::Buffer(transaction.to_bytes_le().unwrap().into()); - println!("0"); // Convert the transaction to a data object. let data = Data::Object(transaction); // Compute the checksums. - println!("1"); let checksum_1 = data_bytes.to_checksum::().unwrap(); - println!("2"); let checksum_2 = data.to_checksum::().unwrap(); - println!("END"); // Ensure the checksums are equal. assert_eq!(checksum_1, checksum_2); diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 580b36ac86..12eddde682 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -33,7 +33,6 @@ impl Process { deployment: &Deployment, fee: &Fee, ) -> Result<(Stack, Vec>)> { - println!("1"); let timer = timer!("Process::finalize_deployment"); // Compute the program stack. @@ -52,8 +51,6 @@ impl Process { } lap!(timer, "Insert the verifying keys"); - println!("2"); - // Determine which mappings must be initialized. let mappings = match deployment.edition().is_zero() { true => deployment.program().mappings().values().collect::>(), @@ -97,8 +94,6 @@ impl Process { } finish!(timer, "Initialize the program mappings"); - println!("3"); - // Return the stack and finalize operations. Ok((stack, finalize_operations)) }) diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 59e98f9ea8..17002dd3bd 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -221,7 +221,6 @@ impl Stack { } } }; - println!("Program edition: {}", edition); // Ensure the program contains functions. ensure!(!program.functions().is_empty(), "No functions present in the deployment for program '{program_id}'"); // Serialize the program into bytes. diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index 2b653bc0d9..91d64aec4d 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -2644,7 +2644,7 @@ fn test_program_exceeding_transaction_spend_limit() { // Attempt to add the program to the process should pass, as the check happens during finalization. let result = process.add_program(&program); - assert!(result.is_err()); + assert!(result.is_ok()); // Initialize a `Stack` directly with the program should pass, as the check happens during finalization. let result = Stack::initialize(&process, &program, 0); diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 8488ced8ed..7e0ed3b170 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -60,8 +60,6 @@ impl> VM { false => self.prepare_for_speculate(&candidate_transactions, rng)?, }; - println!("Verification aborted transactions: {:?}", verification_aborted_transactions); - // Performs a **dry-run** over the list of ratifications, solutions, and transactions. let (ratifications, confirmed_transactions, speculation_aborted_transactions, ratified_finalize_operations) = self.atomic_speculate( @@ -73,8 +71,6 @@ impl> VM { verified_transactions.into_iter(), )?; - println!("Speculation aborted transactions: {:?}", speculation_aborted_transactions); - // Get the aborted transaction ids. let verification_aborted_transaction_ids = verification_aborted_transactions.iter().map(|(tx, e)| (tx.id(), e)); let speculation_aborted_transaction_ids = speculation_aborted_transactions.iter().map(|(tx, e)| (tx.id(), e)); From b0ba2f4ae5f12a45d86b5e6336875225d4fe9f9e Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Sat, 15 Feb 2025 07:43:12 -0800 Subject: [PATCH 45/46] Adjust test --- ledger/src/tests.rs | 116 +++++++++--------- .../process/src/stack/helpers/initialize.rs | 4 - synthesizer/program/src/parse.rs | 7 +- synthesizer/src/vm/mod.rs | 4 +- 4 files changed, 69 insertions(+), 62 deletions(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index d3aacbd33c..0f40c8dbd2 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -32,7 +32,7 @@ use ledger_committee::{Committee, MIN_VALIDATOR_STAKE}; use ledger_narwhal::{BatchCertificate, BatchHeader, Data, Subdag, Transmission, TransmissionID}; use ledger_store::{ConsensusStore, helpers::memory::ConsensusMemory}; use snarkvm_utilities::try_vm_runtime; -use synthesizer::{Stack, program::Program, vm::VM}; +use synthesizer::{Function, program::Program, vm::VM}; use indexmap::{IndexMap, IndexSet}; use rand::seq::SliceRandom; @@ -2155,73 +2155,77 @@ fn test_max_committee_limit_with_bonds() { } #[test] -fn test_deployment_exceeding_max_transaction_spend() { +fn test_deployment_and_execution_exceeding_max_transaction_spend() { let rng = &mut TestRng::default(); // Initialize the test environment. let crate::test_helpers::TestEnv { ledger, private_key, .. } = crate::test_helpers::sample_test_env(rng); - // Construct two programs, one that is allowed and one that exceeds the maximum transaction spend. - let mut allowed_program = None; - let mut exceeding_program = None; + // Initialize a tracker for whether the max spend limit was exceeded. + let mut max_spend_limit_exceeded = false; for i in 0..::MAX_COMMANDS.ilog2() { - // Construct the finalize body. - let finalize_body = - (0..2.pow(i)).map(|i| format!("hash.bhp256 0field into r{i} as field;")).collect::>().join("\n"); - - // Construct the program. - let program = Program::from_str(&format!( - r"program test_max_spend_limit_{i}.aleo; - function foo: - async foo into r0; - output r0 as test_max_spend_limit_{i}.aleo/foo.future; - - finalize foo:{finalize_body}", - )) - .unwrap(); + // Initialize the program. + let mut program = + Program::new(ProgramID::from_str(&format!("test_max_spend_limit_{i}.aleo")).unwrap()).unwrap(); + // Construct a finalize body whose finalize cost is excessively large. + let mut function = format!( + r" +function foo: + async foo into r0; + output r0 as test_max_spend_limit_{i}.aleo/foo.future; +finalize foo: + cast 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 into r0 as [u8; 16u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u8; 16u32]; 16u32]; + cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[[u8; 16u32]; 16u32]; 16u32];" + ); + (0..(2.pow(i))).for_each(|i| { + function.push_str(&format!("\n hash.bhp256 r2 into r{} as field;", i + 3)); + }); + + // Parse the function and add it to the program. + let function = Function::from_str(&function).unwrap(); + program.add_function(function).unwrap(); - // Attempt to initialize a `Stack` for the program. - // If this fails, then by `Stack::initialize` the finalize cost exceeds the `TRANSACTION_SPEND_LIMIT`. - if Stack::::new(&ledger.vm().process().read(), &program).is_err() { - exceeding_program = Some(program); + // If the program length exceeds the maximum length, break. + if program.to_string().len() > CurrentNetwork::MAX_PROGRAM_SIZE { break; - } else { - allowed_program = Some(program); } - } - - // Ensure that the allowed and exceeding programs are not None. - assert!(allowed_program.is_some()); - assert!(exceeding_program.is_some()); - let allowed_program = allowed_program.unwrap(); - let exceeding_program = exceeding_program.unwrap(); - - // Deploy the allowed program. - let deployment = ledger.vm().deploy(&private_key, &allowed_program, None, 0, None, rng).unwrap(); - - // Verify the deployment transaction. - assert!(ledger.vm().check_transaction(&deployment, None, rng).is_ok()); - - // Construct the next block. - let block = - ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment], rng).unwrap(); - - // Check that the next block is valid. - ledger.check_next_block(&block, rng).unwrap(); - - // Add the block to the ledger. - ledger.advance_to_next_block(&block).unwrap(); - - // Check that the program exists in the VM. - assert!(ledger.vm().contains_program(allowed_program.id())); - - // Attempt to deploy the exceeding program. - let result = ledger.vm().deploy(&private_key, &exceeding_program, None, 0, None, rng); + // Deploy the program. + let deployment = ledger.vm().deploy(&private_key, &program, None, 0, None, rng).unwrap(); + // Add the deployment to the ledger. + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + ledger.advance_to_next_block(&block).unwrap(); - // Check that the deployment failed. - assert!(result.is_err()); + // Execute the program. + let execution = ledger + .vm() + .execute( + &private_key, + (format!("test_max_spend_limit_{i}.aleo"), "foo"), + Vec::>::new().iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let is_allowed = *execution.base_fee_amount().unwrap() <= CurrentNetwork::TRANSACTION_SPEND_LIMIT; + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![execution], rng).unwrap(); + if is_allowed { + assert_eq!(block.transactions().num_accepted(), 1); + } else { + assert_eq!(block.aborted_transaction_ids().len(), 1); + max_spend_limit_exceeded = true; + } + ledger.advance_to_next_block(&block).unwrap(); + } + // Verify that the max spend limit was exceeded at least once. + assert_eq!(max_spend_limit_exceeded, true); } #[test] diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 849e053cc6..e0f2cde027 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -51,8 +51,6 @@ impl Stack { stack.insert_closure(closure)?; } - println!("TWO"); - // Add the program functions to the stack. for function in program.functions().values() { // Add the function to the stack. @@ -62,8 +60,6 @@ impl Stack { stack.get_number_of_calls(function.name())?; } - println!("THREE"); - // Return the stack. Ok(stack) } diff --git a/synthesizer/program/src/parse.rs b/synthesizer/program/src/parse.rs index 179c506321..2cc7b7b8d7 100644 --- a/synthesizer/program/src/parse.rs +++ b/synthesizer/program/src/parse.rs @@ -120,7 +120,12 @@ impl, Command: CommandTrait> Fro /// Returns a program from a string literal. fn from_str(string: &str) -> Result { // Ensure the raw program string is less than MAX_PROGRAM_SIZE. - ensure!(string.len() <= N::MAX_PROGRAM_SIZE, "Program length exceeds N::MAX_PROGRAM_SIZE."); + ensure!( + string.len() <= N::MAX_PROGRAM_SIZE, + "Program length '{}' exceeds '{}'.", + string.len(), + N::MAX_PROGRAM_SIZE + ); match Self::parse(string) { Ok((remainder, object)) => { diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index b2f96f3e71..a3bf9c7984 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -2631,8 +2631,10 @@ finalize transfer_public_to_private: rng, ) .unwrap(); + let fee_amount = transaction.base_fee_amount().unwrap(); + assert!(*fee_amount > CurrentNetwork::TRANSACTION_SPEND_LIMIT); let block = sample_next_block(&vm, &private_key, &[transaction], rng).unwrap(); - assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 1); vm.add_next_block(&block).unwrap(); } } From e4ffd4aa95136875203f87427a4ebf1b37eb786c Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Sat, 15 Feb 2025 07:43:48 -0800 Subject: [PATCH 46/46] Clippy --- ledger/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 0f40c8dbd2..ab7d0a31cc 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -2225,7 +2225,7 @@ finalize foo: ledger.advance_to_next_block(&block).unwrap(); } // Verify that the max spend limit was exceeded at least once. - assert_eq!(max_spend_limit_exceeded, true); + assert!(max_spend_limit_exceeded); } #[test]