diff --git a/CHANGELOG.md b/CHANGELOG.md index ad99c08a0..b5bb086ff 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, moved `Block` to `miden-objects` (#618, #621). ## 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/Cargo.lock b/Cargo.lock index 636ce6e5a..fc7b3c82e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,9 +519,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -610,9 +610,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2354079aba5325d514bcacc9f0396fe0679949400efcda363c8702a157aa3f77" +checksum = "40dbd2701d7a0dd2ee9bb429388c21145a83e3228144ed086dfc04d676b8bc3a" dependencies = [ "blake3", "cc", @@ -991,9 +991,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", @@ -1209,37 +1209,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" dependencies = [ - "winapi", + "windows-sys", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index a955e91bf..16c26e44a 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,9 +1,8 @@ use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; use miden_objects::{ + accounts::delta::AccountUpdateDetails, notes::Nullifier, - transaction::{ - AccountDetails, InputNotes, ProvenTransaction, ProvenTransactionBuilder, TransactionWitness, - }, + transaction::{InputNotes, ProvenTransaction, ProvenTransactionBuilder, TransactionWitness}, }; use miden_prover::prove; pub use miden_prover::ProvingOptions; @@ -75,18 +74,18 @@ impl TransactionProver { let builder = match account_id.is_on_chain() { true => { - let account_details = if tx_witness.account().is_new() { + let account_update_details = if tx_witness.account().is_new() { let mut account = tx_witness.account().clone(); account .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_update_details) }, false => builder, }; diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index 546daeaa9..af69ecbe9 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_state_hash(), transaction.input_notes().commitment(), transaction.block_ref(), ); let stack_outputs = TransactionKernel::build_output_stack( - transaction.final_account_hash(), + transaction.account_update().final_state_hash(), transaction.output_notes().commitment(), ); diff --git a/objects/src/accounts/delta/mod.rs b/objects/src/accounts/delta/mod.rs index e9556012c..643e9360e 100644 --- a/objects/src/accounts/delta/mod.rs +++ b/objects/src/accounts/delta/mod.rs @@ -1,7 +1,8 @@ use alloc::string::ToString; use super::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, Word, ZERO, + Account, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, + Word, ZERO, }; use crate::{assets::Asset, AccountDeltaError}; @@ -83,6 +84,28 @@ impl AccountDelta { } } +#[derive(Clone, Debug, PartialEq, Eq)] +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) + } +} + +// SERIALIZATION +// ================================================================================================ + impl Serializable for AccountDelta { fn write_into(&self, target: &mut W) { self.storage.write_into(target); @@ -104,6 +127,37 @@ impl Deserializable for AccountDelta { } } +impl Serializable for AccountUpdateDetails { + fn write_into(&self, target: &mut W) { + match self { + AccountUpdateDetails::Private => { + 0_u8.write_into(target); + }, + 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 AccountUpdateDetails { + fn read_from(source: &mut R) -> Result { + match u8::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" + ))), + } + } +} + // HELPER FUNCTIONS // ================================================================================================ diff --git a/objects/src/block/mod.rs b/objects/src/block/mod.rs index a614b0f29..e58fb688c 100644 --- a/objects/src/block/mod.rs +++ b/objects/src/block/mod.rs @@ -1,6 +1,195 @@ +use alloc::vec::Vec; + use super::{Digest, Felt, Hasher, ZERO}; mod header; pub use header::BlockHeader; mod note_tree; -pub use note_tree::BlockNoteTree; +pub use note_tree::{BlockNoteIndex, BlockNoteTree}; + +use crate::{ + accounts::{delta::AccountUpdateDetails, AccountId}, + notes::Nullifier, + transaction::OutputNote, + utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, +}; + +pub type NoteBatch = Vec; + +// BLOCK +// ================================================================================================ + +/// A block in the Miden chain. +/// +/// A block contains information resulting from executing a set of transactions against the chain +/// state defined by the previous block. It consists of 3 main components: +/// - A set of change descriptors for all accounts updated in this block. For private accounts, +/// the block contains only the new account state hashes; for public accounts, the block also +/// contains a set of state deltas which can be applied to the previous account state to get the +/// new account state. +/// - A set of new notes created in this block. For private notes, the block contains only note IDs +/// and note metadata; for public notes, full note details are recorded. +/// - A set of new nullifiers created for all notes that were consumed in the block. +/// +/// In addition to the above components, a block also contains a block header which contains +/// commitments to the new state of the chain as well as a ZK proof attesting that a set of valid +/// transactions was executed to transition the chain into the state described by this block (the +/// ZK proof part is not yet implemented). +#[derive(Debug, Clone)] +pub struct Block { + /// Block header. + header: BlockHeader, + + /// Account updates for the block. + updated_accounts: Vec, + + /// Note batches created in transactions in the block. + created_notes: Vec, + + /// Nullifiers produced in transactions in the block. + created_nullifiers: Vec, + // + // TODO: add zk proof +} + +impl Block { + /// Returns a new [Block] instantiated from the provided components. + /// + /// Note: consistency of the provided components is not validated. + pub const fn new( + header: BlockHeader, + updated_accounts: Vec, + created_notes: Vec, + created_nullifiers: Vec, + ) -> Self { + Self { + header, + updated_accounts, + created_notes, + created_nullifiers, + } + } + + /// Returns the block header. + pub fn header(&self) -> BlockHeader { + self.header + } + + /// Returns a set of account update descriptions for all accounts updated in this block. + pub fn updated_accounts(&self) -> &[BlockAccountUpdate] { + &self.updated_accounts + } + + /// Returns a set of note batches containing all notes created in this block. + pub fn created_notes(&self) -> &[NoteBatch] { + &self.created_notes + } + + /// Returns an iterator over all notes created in this block. + /// + /// Each note is accompanies with a corresponding index specifying where the note is located + /// in the blocks note tree. + pub fn notes(&self) -> impl Iterator { + self.created_notes.iter().enumerate().flat_map(|(batch_idx, notes)| { + notes.iter().enumerate().map(move |(note_idx_in_batch, note)| { + (BlockNoteIndex::new(batch_idx, note_idx_in_batch), note) + }) + }) + } + + /// Returns a set of nullifiers for all notes consumed in the block. + pub fn created_nullifiers(&self) -> &[Nullifier] { + &self.created_nullifiers + } +} + +impl Serializable for Block { + fn write_into(&self, target: &mut W) { + self.header.write_into(target); + self.updated_accounts.write_into(target); + self.created_notes.write_into(target); + self.created_nullifiers.write_into(target); + } +} + +impl Deserializable for Block { + fn read_from(source: &mut R) -> Result { + Ok(Self { + header: BlockHeader::read_from(source)?, + updated_accounts: >::read_from(source)?, + created_notes: >::read_from(source)?, + created_nullifiers: >::read_from(source)?, + }) + } +} + +// BLOCK ACCOUNT UPDATE +// ================================================================================================ + +/// Describes the changes made to an account state resulting from executing transactions contained +/// in a block. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BlockAccountUpdate { + /// ID of the updated account. + account_id: AccountId, + + /// Hash of the new state of the account. + new_state_hash: Digest, + + /// A set of changes which can be applied to the previous account state (i.e., the state as of + /// the last block) to get the new account state. For private accounts, this is set to + /// [AccountUpdateDetails::Private]. + details: AccountUpdateDetails, +} + +impl BlockAccountUpdate { + /// Returns a new [BlockAccountUpdate] instantiated from the specified components. + pub const fn new( + account_id: AccountId, + new_state_hash: Digest, + details: AccountUpdateDetails, + ) -> Self { + Self { account_id, new_state_hash, details } + } + + /// Returns the ID of the updated account. + pub fn account_id(&self) -> AccountId { + self.account_id + } + + /// Returns the hash of the new account state. + pub fn new_state_hash(&self) -> Digest { + self.new_state_hash + } + + /// Returns the description of the updates for public accounts. + /// + /// These descriptions can be used to build the new account state from the previous account + /// state. + 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() + } +} + +impl Serializable for BlockAccountUpdate { + fn write_into(&self, target: &mut W) { + self.account_id.write_into(target); + self.new_state_hash.write_into(target); + self.details.write_into(target); + } +} + +impl Deserializable for BlockAccountUpdate { + fn read_from(source: &mut R) -> Result { + Ok(Self { + account_id: AccountId::read_from(source)?, + new_state_hash: Digest::read_from(source)?, + details: AccountUpdateDetails::read_from(source)?, + }) + } +} diff --git a/objects/src/block/note_tree.rs b/objects/src/block/note_tree.rs index 231c0dfd9..d4802796e 100644 --- a/objects/src/block/note_tree.rs +++ b/objects/src/block/note_tree.rs @@ -14,7 +14,7 @@ use crate::{ /// Wrapper over [SimpleSmt] for notes tree. /// /// Each note is stored as two adjacent leaves: odd leaf for id, even leaf for metadata hash. -/// Id leaf index is calculated as [(batch_idx * MAX_NOTES_PER_BATCH + note_idx_in_batch) * 2]. +/// ID's leaf index is calculated as [(batch_idx * MAX_NOTES_PER_BATCH + note_idx_in_batch) * 2]. /// Metadata hash leaf is stored the next after id leaf: [id_index + 1]. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -23,7 +23,7 @@ pub struct BlockNoteTree(SimpleSmt); impl BlockNoteTree { /// Returns a new [BlockNoteTree] instantiated with entries set as specified by the provided entries. /// - /// Entry format: (batch_index, note_index, (note_id, note_metadata)). + /// Entry format: (note_index, note_id, note_metadata). /// /// All leaves omitted from the entries list are set to [ZERO; 4]. /// @@ -32,13 +32,12 @@ impl BlockNoteTree { /// - The number of entries exceeds the maximum notes tree capacity, that is 2^21. /// - The provided entries contain multiple values for the same key. pub fn with_entries( - entries: impl IntoIterator, + entries: impl IntoIterator, ) -> Result { - let interleaved = - entries.into_iter().flat_map(|(batch_index, note_index, (note_id, metadata))| { - let id_index = Self::leaf_index(batch_index, note_index); - [(id_index, note_id.into()), (id_index + 1, metadata.into())] - }); + let interleaved = entries.into_iter().flat_map(|(index, note_id, metadata)| { + let id_index = index.leaf_index(); + [(id_index, note_id.into()), (id_index + 1, metadata.into())] + }); SimpleSmt::with_leaves(interleaved).map(Self) } @@ -51,13 +50,9 @@ impl BlockNoteTree { /// Returns merkle path for the note with specified batch/note indexes. /// /// The returned path is to the node which is the parent of both note and note metadata node. - pub fn get_note_path( - &self, - batch_idx: usize, - note_idx_in_batch: usize, - ) -> Result { + pub fn get_note_path(&self, index: BlockNoteIndex) -> Result { // get the path to the leaf containing the note (path len = 21) - let leaf_index = LeafIndex::new(Self::leaf_index(batch_idx, note_idx_in_batch))?; + let leaf_index = LeafIndex::new(index.leaf_index())?; // move up the path by removing the first node, this path now points to the parent of the // note path @@ -65,23 +60,44 @@ impl BlockNoteTree { Ok(note_path.into()) } +} - /// Returns an index to the node which the parent of both the note and note metadata. - pub fn note_index(batch_idx: usize, note_idx_in_batch: usize) -> u64 { - (batch_idx * MAX_NOTES_PER_BATCH + note_idx_in_batch) as u64 +impl Default for BlockNoteTree { + fn default() -> Self { + Self(SimpleSmt::new().expect("Unreachable")) } +} - // HELPERS - // -------------------------------------------------------------------------------------------- +/// Index of a block note. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BlockNoteIndex { + batch_idx: usize, + note_idx_in_batch: usize, +} - fn leaf_index(batch_idx: usize, note_idx_in_batch: usize) -> u64 { - Self::note_index(batch_idx, note_idx_in_batch) * 2 +impl BlockNoteIndex { + /// Creates a new [BlockNoteIndex]. + pub fn new(batch_idx: usize, note_idx_in_batch: usize) -> Self { + Self { batch_idx, note_idx_in_batch } } -} -impl Default for BlockNoteTree { - fn default() -> Self { - Self(SimpleSmt::new().expect("Unreachable")) + /// Returns the batch index. + pub fn batch_idx(&self) -> usize { + self.batch_idx + } + + /// Returns the note index in the batch. + pub fn note_idx_in_batch(&self) -> usize { + self.note_idx_in_batch + } + + /// Returns an index to the node which the parent of both the note and note metadata. + pub fn note_index(&self) -> u64 { + (self.batch_idx() * MAX_NOTES_PER_BATCH + self.note_idx_in_batch()) as u64 + } + + fn leaf_index(&self) -> u64 { + self.note_index() * 2 } } diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 768cb54dc..6fe09cdac 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -20,7 +20,7 @@ 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::{ProvenTransaction, ProvenTransactionBuilder, TxAccountUpdate}; 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..079201891 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -4,7 +4,7 @@ use miden_verifier::ExecutionProof; use super::{AccountId, Digest, InputNotes, Nullifier, OutputNote, OutputNotes, TransactionId}; use crate::{ - accounts::{Account, AccountDelta}, + accounts::delta::AccountUpdateDetails, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, ProvenTransactionError, }; @@ -12,15 +12,6 @@ use crate::{ // PROVEN TRANSACTION // ================================================================================================ -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum AccountDetails { - /// The whole state is needed for new accounts - Full(Account), - - /// For existing accounts, only the delta is needed. - Delta(AccountDelta), -} - /// 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)] @@ -28,20 +19,8 @@ pub struct ProvenTransaction { /// A unique identifier for the transaction, see [TransactionId] for additional details. id: TransactionId, - /// 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: TxAccountUpdate, /// A list of nullifiers for all notes consumed by the transaction. input_notes: InputNotes, @@ -57,9 +36,6 @@ pub struct ProvenTransaction { } impl ProvenTransaction { - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - /// Returns unique identifier of this transaction. pub fn id(&self) -> TransactionId { self.id @@ -67,22 +43,12 @@ impl ProvenTransaction { /// Returns ID of the account against which this transaction was executed. pub fn account_id(&self) -> AccountId { - 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 + self.account_update.account_id() } - /// 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) -> &TxAccountUpdate { + &self.account_update } /// Returns a reference to the notes consumed by the transaction. @@ -109,59 +75,93 @@ 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 => { + if self.account_id().is_on_chain() { + let is_new_account = self.account_update.init_state_hash() == Digest::default(); + match self.account_update.details() { + AccountUpdateDetails::Private => { return Err(ProvenTransactionError::OnChainAccountMissingDetails( - self.account_id, + 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_state_hash() { + return Err(ProvenTransactionError::AccountFinalHashMismatch( + self.account_update.final_state_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) } } +impl Serializable for ProvenTransaction { + fn write_into(&self, target: &mut W) { + self.account_update.write_into(target); + self.input_notes.write_into(target); + self.output_notes.write_into(target); + self.block_ref.write_into(target); + self.proof.write_into(target); + } +} + +impl Deserializable for ProvenTransaction { + fn read_from(source: &mut R) -> Result { + let account_update = TxAccountUpdate::read_from(source)?; + + let input_notes = InputNotes::::read_from(source)?; + let output_notes = OutputNotes::read_from(source)?; + + let block_ref = Digest::read_from(source)?; + let proof = ExecutionProof::read_from(source)?; + + let id = TransactionId::new( + account_update.init_state_hash(), + account_update.final_state_hash(), + input_notes.commitment(), + output_notes.commitment(), + ); + + let proven_transaction = Self { + id, + account_update, + input_notes, + output_notes, + block_ref, + proof, + }; + + proven_transaction + .validate() + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + // PROVEN TRANSACTION BUILDER // ================================================================================================ @@ -178,7 +178,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 +209,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 +220,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 +250,7 @@ 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 input_notes = InputNotes::new(self.input_notes).map_err(ProvenTransactionError::InputNotesError)?; let output_notes = OutputNotes::new(self.output_notes) @@ -262,13 +261,16 @@ impl ProvenTransactionBuilder { input_notes.commitment(), output_notes.commitment(), ); + let account_update = TxAccountUpdate::new( + self.account_id, + self.initial_account_hash, + self.final_account_hash, + self.account_update_details, + ); 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, @@ -279,87 +281,97 @@ impl ProvenTransactionBuilder { } } -// SERIALIZATION +// TRANSACTION ACCOUNT UPDATE // ================================================================================================ -impl Serializable for AccountDetails { - fn write_into(&self, target: &mut W) { - match self { - AccountDetails::Full(account) => { - 0_u8.write_into(target); - account.write_into(target); - }, - AccountDetails::Delta(delta) => { - 1_u8.write_into(target); - delta.write_into(target); - }, - } - } +/// Describes the changes made to the account state resulting from a transaction execution. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TxAccountUpdate { + /// ID of the account updated by a transaction. + account_id: AccountId, + + /// The hash of the account before a transaction was executed. + /// + /// Set to `Digest::default()` for new accounts. + init_state_hash: Digest, + + /// The hash of the account state after a transaction was executed. + final_state_hash: Digest, + + /// A set of changes which can be applied the the account's state prior to the transaction to + /// get the account state after the transaction. For private accounts this is set to + /// [AccountUpdateDetails::Private]. + details: AccountUpdateDetails, } -impl Deserializable for AccountDetails { - 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)?)), - v => Err(DeserializationError::InvalidValue(format!( - "Unknown variant {v} for AccountDetails" - ))), +impl TxAccountUpdate { + /// Returns a new [TxAccountUpdate] instantiated from the specified components. + pub const fn new( + account_id: AccountId, + init_state_hash: Digest, + final_state_hash: Digest, + details: AccountUpdateDetails, + ) -> Self { + Self { + account_id, + init_state_hash, + final_state_hash, + details, } } -} -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.input_notes.write_into(target); - self.output_notes.write_into(target); - self.block_ref.write_into(target); - self.proof.write_into(target); + /// Returns the ID of the updated account. + pub fn account_id(&self) -> AccountId { + self.account_id } -} -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)?; + /// Returns the hash of the account's initial state. + pub fn init_state_hash(&self) -> Digest { + self.init_state_hash + } - let input_notes = InputNotes::::read_from(source)?; - let output_notes = OutputNotes::read_from(source)?; + /// Returns the hash of the account's after a transaction was executed. + pub fn final_state_hash(&self) -> Digest { + self.final_state_hash + } - let block_ref = Digest::read_from(source)?; - let proof = ExecutionProof::read_from(source)?; + /// Returns the description of the updates for public accounts. + /// + /// These descriptions can be used to build the new account state from the previous account + /// state. + pub fn details(&self) -> &AccountUpdateDetails { + &self.details + } - let id = TransactionId::new( - initial_account_hash, - final_account_hash, - input_notes.commitment(), - output_notes.commitment(), - ); + /// Returns `true` if the account update details are for a private account. + pub fn is_private(&self) -> bool { + self.details.is_private() + } +} - let proven_transaction = Self { - id, - account_id, - initial_account_hash, - final_account_hash, - account_details, - input_notes, - output_notes, - block_ref, - proof, - }; +impl Serializable for TxAccountUpdate { + fn write_into(&self, target: &mut W) { + self.account_id.write_into(target); + self.init_state_hash.write_into(target); + self.final_state_hash.write_into(target); + self.details.write_into(target); + } +} - proven_transaction - .validate() - .map_err(|err| DeserializationError::InvalidValue(err.to_string())) +impl Deserializable for TxAccountUpdate { + fn read_from(source: &mut R) -> Result { + Ok(Self { + account_id: AccountId::read_from(source)?, + init_state_hash: Digest::read_from(source)?, + final_state_hash: Digest::read_from(source)?, + details: AccountUpdateDetails::read_from(source)?, + }) } } +// TESTS +// ================================================================================================ + #[cfg(test)] mod tests { use super::ProvenTransaction; diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index 66f051217..791a2ec24 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_state_hash(), + tx.account_update().final_state_hash(), tx.input_notes().commitment(), tx.output_notes().commitment(), )