diff --git a/CHANGELOG.md b/CHANGELOG.md index ad99c08a0..3df156889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 0.3.0 (TBD) * [BREAKING] Removed the transaction script root output from the transaction kernel (#608). +* [BREAKING] Refactored account update details (#618). ## 0.2.1 (2024-04-12) @@ -19,7 +20,8 @@ * Improved `ProvenTransaction` serialization (#543). * Implemented note tree wrapper structs (#560). * [BREAKING] Migrated to v0.9 version of Miden VM (#567). -* [BREAKING] Added account storage type parameter to `create_basic_wallet` and `create_basic_fungible_faucet` (miden-lib crate only) (#587). +* [BREAKING] Added account storage type parameter to `create_basic_wallet` and `create_basic_fungible_faucet` (miden-lib + crate only) (#587). * Removed serialization of source locations from account code (#590). ## 0.1.1 (2024-03-07) - `miden-objects` crate only diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index a955e91bf..1dbc43ada 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -2,7 +2,8 @@ use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; use miden_objects::{ notes::Nullifier, transaction::{ - AccountDetails, InputNotes, ProvenTransaction, ProvenTransactionBuilder, TransactionWitness, + AccountUpdateDetails, InputNotes, ProvenTransaction, ProvenTransactionBuilder, + TransactionWitness, }, }; use miden_prover::prove; @@ -81,12 +82,12 @@ impl TransactionProver { .apply_delta(&account_delta) .map_err(TransactionProverError::InvalidAccountDelta)?; - AccountDetails::Full(account) + AccountUpdateDetails::New(account) } else { - AccountDetails::Delta(account_delta) + AccountUpdateDetails::Delta(account_delta) }; - builder.account_details(account_details) + builder.account_update_details(account_details) }, false => builder, }; diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index 546daeaa9..3a996d3be 100644 --- a/miden-tx/src/verifier/mod.rs +++ b/miden-tx/src/verifier/mod.rs @@ -34,12 +34,12 @@ impl TransactionVerifier { // build stack inputs and outputs let stack_inputs = TransactionKernel::build_input_stack( transaction.account_id(), - transaction.initial_account_hash(), + transaction.account_update().init_hash(), transaction.input_notes().commitment(), transaction.block_ref(), ); let stack_outputs = TransactionKernel::build_output_stack( - transaction.final_account_hash(), + transaction.account_update().final_hash(), transaction.output_notes().commitment(), ); diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 768cb54dc..6b7ce2882 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -20,7 +20,9 @@ pub use executed_tx::ExecutedTransaction; pub use inputs::{InputNote, InputNotes, ToNullifier, TransactionInputs}; pub use outputs::{OutputNote, OutputNotes, TransactionOutputs}; pub use prepared_tx::PreparedTransaction; -pub use proven_tx::{AccountDetails, ProvenTransaction, ProvenTransactionBuilder}; +pub use proven_tx::{ + AccountUpdate, AccountUpdateDetails, ProvenTransaction, ProvenTransactionBuilder, +}; pub use transaction_id::TransactionId; pub use tx_args::{TransactionArgs, TransactionScript}; pub use tx_witness::TransactionWitness; diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 6d8d50353..575e3d14b 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -13,14 +13,68 @@ use crate::{ // ================================================================================================ #[derive(Clone, Debug, PartialEq, Eq)] -pub enum AccountDetails { - /// The whole state is needed for new accounts - Full(Account), +pub enum AccountUpdateDetails { + /// Account is private (no on-chain state change). + Private, + + /// The whole state is needed for new accounts. + New(Account), /// For existing accounts, only the delta is needed. Delta(AccountDelta), } +impl AccountUpdateDetails { + /// Returns `true` if the account update details are for private account. + pub fn is_private(&self) -> bool { + matches!(self, Self::Private) + } +} + +/// Account update data. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountUpdate { + /// The hash of the account before the transaction was executed. + /// + /// Set to `Digest::default()` for new accounts. + init_hash: Digest, + + /// The hash of the account after the transaction was executed. + final_hash: Digest, + + /// Optional account state changes used for on-chain accounts. This data is used to update an + /// on-chain account's state after a local transaction execution. For private accounts, this + /// is [AccountUpdateDetails::Private]. + details: AccountUpdateDetails, +} + +impl AccountUpdate { + /// Creates a new [AccountUpdate]. + pub const fn new(init_hash: Digest, final_hash: Digest, details: AccountUpdateDetails) -> Self { + Self { init_hash, final_hash, details } + } + + /// Returns the initial account state hash. + pub fn init_hash(&self) -> Digest { + self.init_hash + } + + /// Returns the final account state hash. + pub fn final_hash(&self) -> Digest { + self.final_hash + } + + /// Returns the account update details. + pub fn details(&self) -> &AccountUpdateDetails { + &self.details + } + + /// Returns `true` if the account update details are for private account. + pub fn is_private(&self) -> bool { + self.details.is_private() + } +} + /// Result of executing and proving a transaction. Contains all the data required to verify that a /// transaction was executed correctly. #[derive(Debug, Clone, PartialEq, Eq)] @@ -31,17 +85,8 @@ pub struct ProvenTransaction { /// ID of the account that the transaction was executed against. account_id: AccountId, - /// The hash of the account before the transaction was executed. - /// - /// Set to `Digest::default()` for new accounts. - initial_account_hash: Digest, - - /// The hash of the account after the transaction was executed. - final_account_hash: Digest, - - /// Optional account state changes used for on-chain accounts, This data is used to update an - /// on-chain account's state after a local transaction execution. - account_details: Option, + /// Account update data. + account_update: AccountUpdate, /// A list of nullifiers for all notes consumed by the transaction. input_notes: InputNotes, @@ -70,19 +115,9 @@ impl ProvenTransaction { self.account_id } - /// Returns the initial account state hash. - pub fn initial_account_hash(&self) -> Digest { - self.initial_account_hash - } - - /// Returns the final account state hash. - pub fn final_account_hash(&self) -> Digest { - self.final_account_hash - } - - /// Returns the account details. - pub fn account_details(&self) -> Option<&AccountDetails> { - self.account_details.as_ref() + /// Returns the account update details. + pub fn account_update(&self) -> &AccountUpdate { + &self.account_update } /// Returns a reference to the notes consumed by the transaction. @@ -109,53 +144,45 @@ impl ProvenTransaction { // -------------------------------------------------------------------------------------------- fn validate(self) -> Result { - if !self.account_id.is_on_chain() && self.account_details.is_some() { - return Err(ProvenTransactionError::OffChainAccountWithDetails(self.account_id)); - } - if self.account_id.is_on_chain() { - match self.account_details { - None => { + let is_new_account = self.account_update.init_hash == Digest::default(); + match self.account_update.details { + AccountUpdateDetails::Private => { return Err(ProvenTransactionError::OnChainAccountMissingDetails( self.account_id, )) }, - Some(ref details) => { - let is_new_account = self.initial_account_hash == Digest::default(); - - match (is_new_account, details) { - (true, AccountDetails::Delta(_)) => { - return Err( - ProvenTransactionError::NewOnChainAccountRequiresFullDetails( - self.account_id, - ), - ) - }, - (true, AccountDetails::Full(account)) => { - if account.id() != self.account_id { - return Err(ProvenTransactionError::AccountIdMismatch( - self.account_id, - account.id(), - )); - } - if account.hash() != self.final_account_hash { - return Err(ProvenTransactionError::AccountFinalHashMismatch( - self.final_account_hash, - account.hash(), - )); - } - }, - (false, AccountDetails::Full(_)) => { - return Err( - ProvenTransactionError::ExistingOnChainAccountRequiresDeltaDetails( - self.account_id, - ), - ) - }, - (false, AccountDetails::Delta(_)) => (), + AccountUpdateDetails::New(ref account) => { + if !is_new_account { + return Err( + ProvenTransactionError::ExistingOnChainAccountRequiresDeltaDetails( + self.account_id, + ), + ); + } + if account.id() != self.account_id { + return Err(ProvenTransactionError::AccountIdMismatch( + self.account_id, + account.id(), + )); + } + if account.hash() != self.account_update.final_hash { + return Err(ProvenTransactionError::AccountFinalHashMismatch( + self.account_update.final_hash, + account.hash(), + )); + } + }, + AccountUpdateDetails::Delta(_) => { + if is_new_account { + return Err(ProvenTransactionError::NewOnChainAccountRequiresFullDetails( + self.account_id, + )); } }, } + } else if !self.account_update.is_private() { + return Err(ProvenTransactionError::OffChainAccountWithDetails(self.account_id)); } Ok(self) @@ -178,7 +205,7 @@ pub struct ProvenTransactionBuilder { final_account_hash: Digest, /// State changes to the account due to the transaction. - account_details: Option, + account_update_details: AccountUpdateDetails, /// List of [Nullifier]s of all consumed notes by the transaction. input_notes: Vec, @@ -209,7 +236,7 @@ impl ProvenTransactionBuilder { account_id, initial_account_hash, final_account_hash, - account_details: None, + account_update_details: AccountUpdateDetails::Private, input_notes: Vec::new(), output_notes: Vec::new(), block_ref, @@ -220,9 +247,9 @@ impl ProvenTransactionBuilder { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Sets the account's details. - pub fn account_details(mut self, account_details: AccountDetails) -> Self { - self.account_details = Some(account_details); + /// Sets the account's update details. + pub fn account_update_details(mut self, details: AccountUpdateDetails) -> Self { + self.account_update_details = details; self } @@ -250,8 +277,12 @@ impl ProvenTransactionBuilder { /// /// An error will be returned if an on-chain account is used without provided on-chain detail. /// Or if the account details, i.e. account id and final hash, don't match the transaction. - pub fn build(mut self) -> Result { - let account_details = self.account_details.take(); + pub fn build(self) -> Result { + let account_update = AccountUpdate { + init_hash: self.initial_account_hash, + final_hash: self.final_account_hash, + details: self.account_update_details, + }; let input_notes = InputNotes::new(self.input_notes).map_err(ProvenTransactionError::InputNotesError)?; let output_notes = OutputNotes::new(self.output_notes) @@ -266,9 +297,7 @@ impl ProvenTransactionBuilder { let proven_transaction = ProvenTransaction { id, account_id: self.account_id, - initial_account_hash: self.initial_account_hash, - final_account_hash: self.final_account_hash, - account_details, + account_update, input_notes, output_notes, block_ref: self.block_ref, @@ -282,26 +311,30 @@ impl ProvenTransactionBuilder { // SERIALIZATION // ================================================================================================ -impl Serializable for AccountDetails { +impl Serializable for AccountUpdateDetails { fn write_into(&self, target: &mut W) { match self { - AccountDetails::Full(account) => { + AccountUpdateDetails::Private => { 0_u8.write_into(target); - account.write_into(target); }, - AccountDetails::Delta(delta) => { + AccountUpdateDetails::New(account) => { 1_u8.write_into(target); + account.write_into(target); + }, + AccountUpdateDetails::Delta(delta) => { + 2_u8.write_into(target); delta.write_into(target); }, } } } -impl Deserializable for AccountDetails { +impl Deserializable for AccountUpdateDetails { fn read_from(source: &mut R) -> Result { match u8::read_from(source)? { - 0_u8 => Ok(Self::Full(Account::read_from(source)?)), - 1_u8 => Ok(Self::Delta(AccountDelta::read_from(source)?)), + 0 => Ok(Self::Private), + 1 => Ok(Self::New(Account::read_from(source)?)), + 2 => Ok(Self::Delta(AccountDelta::read_from(source)?)), v => Err(DeserializationError::InvalidValue(format!( "Unknown variant {v} for AccountDetails" ))), @@ -309,12 +342,28 @@ impl Deserializable for AccountDetails { } } +impl Serializable for AccountUpdate { + fn write_into(&self, target: &mut W) { + self.init_hash.write_into(target); + self.final_hash.write_into(target); + self.details.write_into(target); + } +} + +impl Deserializable for AccountUpdate { + fn read_from(source: &mut R) -> Result { + Ok(Self { + init_hash: Digest::read_from(source)?, + final_hash: Digest::read_from(source)?, + details: AccountUpdateDetails::read_from(source)?, + }) + } +} + impl Serializable for ProvenTransaction { fn write_into(&self, target: &mut W) { self.account_id.write_into(target); - self.initial_account_hash.write_into(target); - self.final_account_hash.write_into(target); - self.account_details.write_into(target); + self.account_update.write_into(target); self.input_notes.write_into(target); self.output_notes.write_into(target); self.block_ref.write_into(target); @@ -325,9 +374,7 @@ impl Serializable for ProvenTransaction { impl Deserializable for ProvenTransaction { fn read_from(source: &mut R) -> Result { let account_id = AccountId::read_from(source)?; - let initial_account_hash = Digest::read_from(source)?; - let final_account_hash = Digest::read_from(source)?; - let account_details = >::read_from(source)?; + let account_update = AccountUpdate::read_from(source)?; let input_notes = InputNotes::::read_from(source)?; let output_notes = OutputNotes::read_from(source)?; @@ -336,8 +383,8 @@ impl Deserializable for ProvenTransaction { let proof = ExecutionProof::read_from(source)?; let id = TransactionId::new( - initial_account_hash, - final_account_hash, + account_update.init_hash, + account_update.final_hash, input_notes.commitment(), output_notes.commitment(), ); @@ -345,9 +392,7 @@ impl Deserializable for ProvenTransaction { let proven_transaction = Self { id, account_id, - initial_account_hash, - final_account_hash, - account_details, + account_update, input_notes, output_notes, block_ref, diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index 66f051217..86bc620d3 100644 --- a/objects/src/transaction/transaction_id.rs +++ b/objects/src/transaction/transaction_id.rs @@ -76,8 +76,8 @@ impl Display for TransactionId { impl From<&ProvenTransaction> for TransactionId { fn from(tx: &ProvenTransaction) -> Self { Self::new( - tx.initial_account_hash(), - tx.final_account_hash(), + tx.account_update().init_hash(), + tx.account_update().final_hash(), tx.input_notes().commitment(), tx.output_notes().commitment(), )