diff --git a/CHANGELOG.md b/CHANGELOG.md index d577f88fd..511a04e20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - [BREAKING] Renamed `--url` CLI flags and `*_URL` env vars to `--listen` / `*_LISTEN` across all components. - [BREAKING] Removed `miden-node validator` subcommand and created a separate `miden-validator` binary ([#2053](https://github.com/0xMiden/node/pull/2053)). - [BREAKING] Removed `miden-node ntx-builder` subcommand and created a separate `miden-ntx-builder` binary ([#2067](https://github.com/0xMiden/node/pull/2067)). +- [BREAKING] Reworked note proto types for multi-attachment support: `NoteMetadata` now carries `attachment_schemes` (repeated) and `attachments_commitment` instead of a single `attachment`. `Note` and `NetworkNote` gained an `attachments` field. `NoteSyncRecord` now embeds full `NoteMetadata` instead of `NoteMetadataHeader`. Removed `NoteAttachmentKind` enum and `NoteMetadataHeader` message ([#2078](https://github.com/0xMiden/node/pull/2078)). ## v0.14.10 (2026-05-29) diff --git a/Cargo.lock b/Cargo.lock index e410be4c1..64c6977a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,7 +177,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -188,7 +188,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1678,7 +1678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2895,7 +2895,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miden-agglayer" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#ad7420e778af4a4b7be4f4bb24d0e7155b487e22" +source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" dependencies = [ "alloy-sol-types", "fs-err", @@ -2971,7 +2971,7 @@ dependencies = [ [[package]] name = "miden-block-prover" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#ad7420e778af4a4b7be4f4bb24d0e7155b487e22" +source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" dependencies = [ "miden-protocol", "thiserror 2.0.18", @@ -3561,7 +3561,7 @@ dependencies = [ [[package]] name = "miden-protocol" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#ad7420e778af4a4b7be4f4bb24d0e7155b487e22" +source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" dependencies = [ "bech32", "fs-err", @@ -3673,7 +3673,7 @@ dependencies = [ [[package]] name = "miden-standards" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#ad7420e778af4a4b7be4f4bb24d0e7155b487e22" +source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" dependencies = [ "bon", "fs-err", @@ -3690,7 +3690,7 @@ dependencies = [ [[package]] name = "miden-testing" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#ad7420e778af4a4b7be4f4bb24d0e7155b487e22" +source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" dependencies = [ "anyhow", "itertools 0.14.0", @@ -3710,7 +3710,7 @@ dependencies = [ [[package]] name = "miden-tx" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#ad7420e778af4a4b7be4f4bb24d0e7155b487e22" +source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" dependencies = [ "miden-processor", "miden-protocol", @@ -3723,7 +3723,7 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#ad7420e778af4a4b7be4f4bb24d0e7155b487e22" +source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" dependencies = [ "miden-protocol", "miden-tx", @@ -3967,7 +3967,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4844,7 +4844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", - "itertools 0.13.0", + "itertools 0.14.0", "log", "multimap", "petgraph 0.8.3", @@ -4865,7 +4865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5394,7 +5394,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5465,7 +5465,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5872,7 +5872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6087,7 +6087,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6096,7 +6096,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6115,7 +6115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7119,7 +7119,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 3d8c56b0f..327d2193d 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -21,11 +21,12 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachment, - NoteMetadata, + NoteAttachments, NoteRecipient, NoteScript, NoteStorage, NoteType, + PartialNoteMetadata, }; use miden_protocol::transaction::{InputNotes, PartialBlockchain, TransactionArgs}; use miden_protocol::utils::serde::{Deserializable, Serializable}; @@ -946,9 +947,9 @@ fn create_network_note( let target = NetworkAccountTarget::new(counter_account.id(), NoteExecutionHint::Always) .context("Failed to create NetworkAccountTarget for counter account")?; let attachment: NoteAttachment = target.into(); + let attachments = NoteAttachments::from(attachment); - let metadata = - NoteMetadata::new(wallet_account.id(), NoteType::Public).with_attachment(attachment); + let partial_metadata = PartialNoteMetadata::new(wallet_account.id(), NoteType::Public); let serial_num = Word::new([ Felt::new(rng.random()), @@ -959,7 +960,12 @@ fn create_network_note( let recipient = NoteRecipient::new(serial_num, script, NoteStorage::new(vec![])?); - let network_note = Note::new(NoteAssets::new(vec![])?, metadata, recipient.clone()); + let network_note = Note::with_attachments( + NoteAssets::new(vec![])?, + partial_metadata, + recipient.clone(), + attachments, + ); Ok((network_note, recipient)) } diff --git a/bin/stress-test/src/seeding/mod.rs b/bin/stress-test/src/seeding/mod.rs index 76015acf3..a79ecc57d 100644 --- a/bin/stress-test/src/seeding/mod.rs +++ b/bin/stress-test/src/seeding/mod.rs @@ -28,7 +28,7 @@ use miden_protocol::account::{ StorageSlot, StorageSlotName, }; -use miden_protocol::asset::{Asset, FungibleAsset, TokenSymbol}; +use miden_protocol::asset::{Asset, AssetAmount, FungibleAsset, TokenSymbol}; use miden_protocol::batch::{BatchAccountUpdate, BatchId, ProvenBatch}; use miden_protocol::block::{ BlockHeader, @@ -58,8 +58,7 @@ use miden_protocol::utils::serde::Serializable; use miden_protocol::vm::ExecutionProof; use miden_protocol::{Felt, ONE, Word}; use miden_standards::account::auth::AuthSingleSig; -use miden_standards::account::faucets::{BasicFungibleFaucet, TokenMetadata}; -use miden_standards::account::metadata::{FungibleTokenMetadata, TokenName}; +use miden_standards::account::faucets::{FungibleFaucet, TokenName}; use miden_standards::account::policies::{ BurnPolicyConfig, MintPolicyConfig, @@ -455,7 +454,7 @@ fn create_note(faucet_ids: &[AccountId], target_id: AccountId, rng: &mut RandomC target_id, assets, miden_protocol::note::NoteType::Public, - miden_protocol::note::NoteAttachment::default(), + miden_protocol::note::NoteAttachments::empty(), rng, ) .expect("note creation failed") @@ -579,20 +578,18 @@ fn create_faucet_with_seed(index: u64) -> Account { let init_seed: Vec<_> = index.to_be_bytes().into_iter().chain([0u8; 24]).collect(); let token_symbol = TokenSymbol::new("TEST").unwrap(); - let token_metadata = FungibleTokenMetadata::builder( - TokenName::new("TEST").unwrap(), - token_symbol, - 2, - FungibleAsset::MAX_AMOUNT, - ) - .build() - .unwrap(); + let faucet = FungibleFaucet::builder() + .name(TokenName::new("TEST").unwrap()) + .symbol(token_symbol) + .decimals(2) + .max_supply(AssetAmount::new(FungibleAsset::MAX_AMOUNT).unwrap()) + .build() + .unwrap(); AccountBuilder::new(init_seed.try_into().unwrap()) .account_type(AccountType::FungibleFaucet) .storage_mode(AccountStorageMode::Private) - .with_component(token_metadata) - .with_component(BasicFungibleFaucet) + .with_component(faucet) .with_components(TokenPolicyManager::new( PolicyAuthority::AuthControlled, MintPolicyConfig::AllowAll, @@ -756,11 +753,11 @@ fn create_emit_note_tx( ) -> ProvenTransaction { let initial_account_hash = faucet.to_commitment(); - let metadata_slot_name = TokenMetadata::metadata_slot(); - let slot = faucet.storage().get_item(metadata_slot_name).unwrap(); + let token_config_slot = FungibleFaucet::token_config_slot(); + let slot = faucet.storage().get_item(token_config_slot).unwrap(); faucet .storage_mut() - .set_item(metadata_slot_name, [slot[0] + Felt::new(10), slot[1], slot[2], slot[3]].into()) + .set_item(token_config_slot, [slot[0] + Felt::new(10), slot[1], slot[2], slot[3]].into()) .unwrap(); faucet.increment_nonce(ONE).unwrap(); diff --git a/crates/block-producer/src/test_utils/account.rs b/crates/block-producer/src/test_utils/account.rs index 0d1e9100b..09b2d5f67 100644 --- a/crates/block-producer/src/test_utils/account.rs +++ b/crates/block-producer/src/test_utils/account.rs @@ -35,14 +35,14 @@ impl MockPrivateAccount { init_seed, AccountType::RegularAccountUpdatableCode, AccountStorageMode::Private, - AccountIdVersion::Version0, + AccountIdVersion::Version1, Word::empty(), Word::empty(), ) .unwrap(); Self::new( - AccountId::new(account_seed, AccountIdVersion::Version0, Word::empty(), Word::empty()) + AccountId::new(account_seed, AccountIdVersion::Version1, Word::empty(), Word::empty()) .unwrap(), if new_account { Word::empty() diff --git a/crates/block-producer/src/test_utils/proven_tx.rs b/crates/block-producer/src/test_utils/proven_tx.rs index 6af29a0ac..e434dd52c 100644 --- a/crates/block-producer/src/test_utils/proven_tx.rs +++ b/crates/block-producer/src/test_utils/proven_tx.rs @@ -7,12 +7,12 @@ use miden_protocol::account::AccountId; use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::asset::FungibleAsset; use miden_protocol::block::BlockNumber; -use miden_protocol::note::{Note, Nullifier}; +use miden_protocol::note::{Note, NoteAttachments, Nullifier}; use miden_protocol::transaction::{ InputNote, InputNoteCommitment, OutputNote, - PrivateNoteHeader, + PrivateOutputNote, ProvenTransaction, TxAccountUpdate, }; @@ -134,7 +134,9 @@ impl MockProvenTxBuilder { .map(|note_index| { let note = Note::mock_noop(Word::from([0, 0, 0, note_index])); - OutputNote::Private(PrivateNoteHeader::new(note.header().clone()).unwrap()) + OutputNote::Private( + PrivateOutputNote::new(*note.header(), NoteAttachments::empty()).unwrap(), + ) }) .collect(); diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 3ec63c2ad..5184bc1b0 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -3,17 +3,18 @@ use std::sync::Arc; use miden_protocol::crypto::merkle::SparseMerklePath; use miden_protocol::note::{ Note, - NoteAttachment, - NoteAttachmentKind, + NoteAttachmentHeader, + NoteAttachmentScheme, + NoteAttachments, NoteDetails, NoteHeader, NoteId, NoteInclusionProof, NoteMetadata, - NoteMetadataHeader, NoteScript, NoteTag, NoteType, + PartialNoteMetadata, }; use miden_protocol::utils::serde::Serializable; use miden_protocol::{MastForest, MastNodeId, Word}; @@ -49,52 +50,31 @@ impl TryFrom for NoteType { } } -// NOTE ATTACHMENT KIND -// ================================================================================================ - -impl From for proto::note::NoteAttachmentKind { - fn from(kind: NoteAttachmentKind) -> Self { - match kind { - NoteAttachmentKind::None => proto::note::NoteAttachmentKind::None, - NoteAttachmentKind::Word => proto::note::NoteAttachmentKind::Word, - NoteAttachmentKind::Array => proto::note::NoteAttachmentKind::Array, - } - } -} - -impl TryFrom for NoteAttachmentKind { - type Error = ConversionError; - - fn try_from(kind: proto::note::NoteAttachmentKind) -> Result { - match kind { - proto::note::NoteAttachmentKind::None => Ok(NoteAttachmentKind::None), - proto::note::NoteAttachmentKind::Word => Ok(NoteAttachmentKind::Word), - proto::note::NoteAttachmentKind::Array => Ok(NoteAttachmentKind::Array), - proto::note::NoteAttachmentKind::Unspecified => { - Err(ConversionError::message("enum variant discriminant out of range")) - }, - } - } -} - -// NOTE METADATA HEADER +// NOTE METADATA // ================================================================================================ -impl From for proto::note::NoteMetadataHeader { - fn from(header: NoteMetadataHeader) -> Self { - Self { - sender: Some(header.sender().into()), - note_type: proto::note::NoteType::from(header.note_type()) as i32, - tag: header.tag().as_u32(), - attachment_kind: proto::note::NoteAttachmentKind::from(header.attachment_kind()) as i32, - attachment_scheme: header.attachment_scheme().as_u32(), +impl From for proto::note::NoteMetadata { + fn from(val: NoteMetadata) -> Self { + let sender = Some(val.sender().into()); + let note_type = proto::note::NoteType::from(val.note_type()) as i32; + let tag = val.tag().as_u32(); + let attachment_schemes = val + .attachment_headers() + .iter() + .map(|header| u32::from(header.scheme().map_or(0, |s| s.as_u16()))) + .collect(); + let attachments_commitment = Some(val.attachments_commitment().into()); + + proto::note::NoteMetadata { + sender, + note_type, + tag, + attachment_schemes, + attachments_commitment, } } } -// NOTE METADATA -// ================================================================================================ - impl TryFrom for NoteMetadata { type Error = ConversionError; @@ -106,43 +86,51 @@ impl TryFrom for NoteMetadata { .try_into() .context("note_type")?; let tag = NoteTag::new(value.tag); + let attachments_commitment: Word = decode!(decoder, value.attachments_commitment)?; - // Deserialize attachment if present - let attachment = if value.attachment.is_empty() { - NoteAttachment::default() - } else { - NoteAttachment::decode_bytes(&value.attachment, "NoteAttachment")? - }; + if value.attachment_schemes.len() > NoteAttachments::MAX_COUNT { + return Err(ConversionError::message("too many attachment schemes")); + } + let mut attachment_headers = [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT]; + for (slot, raw) in attachment_headers.iter_mut().zip(value.attachment_schemes) { + let raw = u16::try_from(raw) + .map_err(|_| ConversionError::message("attachment scheme out of u16 range"))?; + *slot = if raw == 0 { + NoteAttachmentHeader::absent() + } else { + NoteAttachmentHeader::new(NoteAttachmentScheme::new(raw)?) + }; + } - Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment)) + let partial = PartialNoteMetadata::new(sender, note_type).with_tag(tag); + Ok(NoteMetadata::from_parts(partial, attachment_headers, attachments_commitment)) } } +// NOTE +// ================================================================================================ + impl From for proto::note::NetworkNote { fn from(note: Note) -> Self { - Self { - metadata: Some(proto::note::NoteMetadata::from(note.metadata().clone())), - details: NoteDetails::from(note).to_bytes(), - } + let metadata = Some(proto::note::NoteMetadata::from(*note.metadata())); + let attachments = note.attachments().to_bytes(); + let details = NoteDetails::from(note).to_bytes(); + Self { metadata, details, attachments } } } impl From for proto::note::Note { fn from(note: Note) -> Self { - Self { - metadata: Some(proto::note::NoteMetadata::from(note.metadata().clone())), - details: Some(NoteDetails::from(note).to_bytes()), - } + let metadata = Some(proto::note::NoteMetadata::from(*note.metadata())); + let attachments = note.attachments().to_bytes(); + let details = Some(NoteDetails::from(note).to_bytes()); + Self { metadata, details, attachments } } } impl From for proto::note::NetworkNote { fn from(note: AccountTargetNetworkNote) -> Self { - let note = note.into_note(); - Self { - metadata: Some(proto::note::NoteMetadata::from(note.metadata().clone())), - details: NoteDetails::from(note).to_bytes(), - } + note.into_note().into() } } @@ -150,26 +138,44 @@ impl TryFrom for AccountTargetNetworkNote { type Error = ConversionError; fn try_from(value: proto::note::NetworkNote) -> Result { - let decoder = value.decoder(); - let details = NoteDetails::decode_bytes(&value.details, "NoteDetails")?; - let (assets, recipient) = details.into_parts(); - let metadata: NoteMetadata = decode!(decoder, value.metadata)?; - let note = Note::new(assets, metadata, recipient); + let proto::note::NetworkNote { metadata, details, attachments } = value; + + let metadata = metadata + .ok_or(ConversionError::missing_field::("metadata"))?; + let partial_metadata = partial_note_metadata_from_proto(metadata)?; + + let note_details = NoteDetails::decode_bytes(&details, "NoteDetails")?; + let (assets, recipient) = note_details.into_parts(); + let attachments = decode_attachments(&attachments)?; + + let note = Note::with_attachments(assets, partial_metadata, recipient, attachments); AccountTargetNetworkNote::new(note).map_err(ConversionError::from) } } -impl From for proto::note::NoteMetadata { - fn from(val: NoteMetadata) -> Self { - let sender = Some(val.sender().into()); - let note_type = proto::note::NoteType::from(val.note_type()) as i32; - let tag = val.tag().as_u32(); - let attachment = val.attachment().to_bytes(); +impl TryFrom for Note { + type Error = ConversionError; - proto::note::NoteMetadata { sender, note_type, tag, attachment } + fn try_from(proto_note: proto::note::Note) -> Result { + let proto::note::Note { metadata, details, attachments } = proto_note; + + let metadata = + metadata.ok_or(ConversionError::missing_field::("metadata"))?; + let partial_metadata = partial_note_metadata_from_proto(metadata)?; + + let details = + details.ok_or(ConversionError::missing_field::("details"))?; + let note_details = NoteDetails::decode_bytes(&details, "NoteDetails")?; + let (assets, recipient) = note_details.into_parts(); + let attachments = decode_attachments(&attachments)?; + + Ok(Note::with_attachments(assets, partial_metadata, recipient, attachments)) } } +// NOTE ID +// ================================================================================================ + impl From for proto::note::NoteId { fn from(digest: Word) -> Self { Self { id: Some(digest.into()) } @@ -246,24 +252,6 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion } } -impl TryFrom for Note { - type Error = ConversionError; - - fn try_from(proto_note: proto::note::Note) -> Result { - let decoder = proto_note.decoder(); - let metadata: NoteMetadata = decode!(decoder, proto_note.metadata)?; - - let details = proto_note - .details - .ok_or(ConversionError::missing_field::("details"))?; - - let note_details = NoteDetails::decode_bytes(&details, "NoteDetails")?; - - let (assets, recipient) = note_details.into_parts(); - Ok(Note::new(assets, metadata, recipient)) - } -} - // NOTE HEADER // ================================================================================================ @@ -313,3 +301,33 @@ impl TryFrom for NoteScript { Ok(Self::from_parts(Arc::new(mast), entrypoint)) } } + +// HELPERS +// ================================================================================================ + +/// Decodes the `(sender, note_type, tag)` triple from a proto `NoteMetadata` into a +/// [`PartialNoteMetadata`]. The attachment-related fields on the proto are ignored — when full +/// attachments are also transmitted, the receiver derives the canonical headers and commitment +/// from those instead. +fn partial_note_metadata_from_proto( + value: proto::note::NoteMetadata, +) -> Result { + let decoder = value.decoder(); + let sender = decode!(decoder, value.sender)?; + let note_type = proto::note::NoteType::try_from(value.note_type) + .map_err(|_| ConversionError::message("enum variant discriminant out of range"))? + .try_into() + .context("note_type")?; + let tag = NoteTag::new(value.tag); + Ok(PartialNoteMetadata::new(sender, note_type).with_tag(tag)) +} + +/// Decodes a serialized [`NoteAttachments`] payload. Empty bytes are treated as an empty +/// collection so that proto3's default value round-trips cleanly. +fn decode_attachments(bytes: &[u8]) -> Result { + if bytes.is_empty() { + Ok(NoteAttachments::empty()) + } else { + NoteAttachments::decode_bytes(bytes, "NoteAttachments") + } +} diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index bc388c7b7..13fcb98a9 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -61,7 +61,7 @@ impl From for proto::transaction::InputNoteCommitment { fn from(value: InputNoteCommitment) -> Self { Self { nullifier: Some(value.nullifier().into()), - header: value.header().cloned().map(Into::into), + header: value.header().copied().map(Into::into), } } } diff --git a/crates/rpc/src/tests.rs b/crates/rpc/src/tests.rs index ba7e40814..f46f9c257 100644 --- a/crates/rpc/src/tests.rs +++ b/crates/rpc/src/tests.rs @@ -76,7 +76,7 @@ fn build_test_proven_tx( ) -> ProvenTransaction { let account_id = AccountId::dummy( [0; 15], - AccountIdVersion::Version0, + AccountIdVersion::Version1, AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, ); diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 7653e2537..ef18b6cbb 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -15,6 +15,7 @@ use miden_protocol::asset::{Asset, AssetVaultKey}; use miden_protocol::block::{BlockHeader, BlockNoteIndex, BlockNumber, SignedBlock}; use miden_protocol::crypto::merkle::SparseMerklePath; use miden_protocol::note::{ + NoteAttachments, NoteDetails, NoteId, NoteInclusionProof, @@ -170,7 +171,7 @@ impl TransactionRecord { initial_state_commitment: Some(self.header.initial_state_commitment().into()), final_state_commitment: Some(self.header.final_state_commitment().into()), input_notes: self.header.input_notes().iter().cloned().map(Into::into).collect(), - output_notes: self.header.output_notes().iter().cloned().map(Into::into).collect(), + output_notes: self.header.output_notes().iter().copied().map(Into::into).collect(), fee: Some(Asset::from(self.header.fee()).into()), }), block_num: self.block_num.as_u32(), @@ -187,6 +188,7 @@ pub struct NoteRecord { pub note_commitment: Word, pub metadata: NoteMetadata, pub details: Option, + pub attachments: NoteAttachments, pub inclusion_path: SparseMerklePath, } @@ -201,6 +203,7 @@ impl From for proto::note::CommittedNote { let note = Some(proto::note::Note { metadata: Some(note.metadata.into()), details: note.details.map(|details| details.to_bytes()), + attachments: note.attachments.to_bytes(), }); Self { inclusion_proof, note } } @@ -223,14 +226,14 @@ pub struct NoteSyncRecord { impl From for proto::note::NoteSyncRecord { fn from(note: NoteSyncRecord) -> Self { - let metadata_header = Some(note.metadata.to_header().into()); + let metadata = Some(note.metadata.into()); let inclusion_proof = Some(proto::note::NoteInclusionInBlockProof { note_id: Some(note.note_id.into()), block_num: note.block_num.as_u32(), note_index_in_block: note.note_index.leaf_index_value().into(), inclusion_path: Some(note.inclusion_path.into()), }); - Self { metadata_header, inclusion_proof } + Self { metadata, inclusion_proof } } } diff --git a/crates/store/src/db/models/queries/accounts/delta/tests.rs b/crates/store/src/db/models/queries/accounts/delta/tests.rs index 3e82a5d9a..3cacb46b0 100644 --- a/crates/store/src/db/models/queries/accounts/delta/tests.rs +++ b/crates/store/src/db/models/queries/accounts/delta/tests.rs @@ -606,7 +606,7 @@ fn upsert_private_account() { // Create a private account ID let account_id = AccountId::dummy( ACCOUNT_ID_SEED, - AccountIdVersion::Version0, + AccountIdVersion::Version1, AccountType::RegularAccountImmutableCode, AccountStorageMode::Private, ); diff --git a/crates/store/src/db/models/queries/accounts/tests.rs b/crates/store/src/db/models/queries/accounts/tests.rs index 87a1fc5de..e5c8d4dbb 100644 --- a/crates/store/src/db/models/queries/accounts/tests.rs +++ b/crates/store/src/db/models/queries/accounts/tests.rs @@ -139,7 +139,7 @@ fn create_test_account_with_storage() -> (Account, AccountId) { // Create a simple public account with one value storage slot let account_id = AccountId::dummy( [1u8; 15], - AccountIdVersion::Version0, + AccountIdVersion::Version1, AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, ); @@ -323,7 +323,7 @@ fn test_select_account_header_at_block_returns_none_for_nonexistent() { let account_id = AccountId::dummy( [99u8; 15], - AccountIdVersion::Version0, + AccountIdVersion::Version1, AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, ); @@ -609,7 +609,7 @@ fn test_upsert_accounts_with_multiple_storage_slots() { // Create account with 3 storage slots let account_id = AccountId::dummy( [2u8; 15], - AccountIdVersion::Version0, + AccountIdVersion::Version1, AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, ); @@ -688,7 +688,7 @@ fn test_upsert_accounts_with_empty_storage() { // Create account with no component storage slots (only auth slot) let account_id = AccountId::dummy( [3u8; 15], - AccountIdVersion::Version0, + AccountIdVersion::Version1, AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, ); diff --git a/crates/store/src/db/models/queries/notes.rs b/crates/store/src/db/models/queries/notes.rs index 5b1eba78a..4fccfdd46 100644 --- a/crates/store/src/db/models/queries/notes.rs +++ b/crates/store/src/db/models/queries/notes.rs @@ -36,7 +36,7 @@ use miden_protocol::block::{BlockNoteIndex, BlockNumber}; use miden_protocol::crypto::merkle::SparseMerklePath; use miden_protocol::note::{ NoteAssets, - NoteAttachment, + NoteAttachments, NoteDetails, NoteId, NoteInclusionProof, @@ -47,6 +47,7 @@ use miden_protocol::note::{ NoteTag, NoteType, Nullifier, + PartialNoteMetadata, }; use miden_protocol::utils::serde::{Deserializable, Serializable}; use miden_standards::note::NetworkAccountTarget; @@ -611,7 +612,7 @@ impl TryInto for NoteSyncRecordRawRow { let note_id = Word::read_from_bytes(&self.note_id[..])?; let inclusion_path = SparseMerklePath::read_from_bytes(&self.inclusion_path[..])?; - let metadata = self.metadata.try_into()?; + let (metadata, _attachments) = self.metadata.try_into()?; Ok(NoteSyncRecord { block_num, note_index, @@ -729,7 +730,7 @@ impl TryInto for NoteRecordWithScriptRawJoined { let metadata = NoteMetadataRawRow { note_type, sender, tag, attachment }; let details = NoteDetailsRawRow { assets, storage, serial_num }; - let metadata = metadata.try_into()?; + let (metadata, attachments) = metadata.try_into()?; let committed_at = BlockNumber::from_raw_sql(committed_at)?; let note_id = Word::read_from_bytes(¬e_id[..])?; let note_commitment = Word::read_from_bytes(¬e_commitment[..])?; @@ -765,6 +766,7 @@ impl TryInto for NoteRecordWithScriptRawJoined { note_commitment, metadata, details, + attachments, inclusion_path, }) } @@ -804,15 +806,21 @@ pub struct NoteMetadataRawRow { } #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] -impl TryInto for NoteMetadataRawRow { +impl TryInto<(NoteMetadata, NoteAttachments)> for NoteMetadataRawRow { type Error = DatabaseError; - fn try_into(self) -> Result { + fn try_into(self) -> Result<(NoteMetadata, NoteAttachments), Self::Error> { let sender = AccountId::read_from_bytes(&self.sender[..])?; let note_type = NoteType::try_from(self.note_type as u8) .map_err(miden_node_db::DatabaseError::conversiont_from_sql::)?; let tag = NoteTag::new(self.tag as u32); - let attachment = NoteAttachment::read_from_bytes(&self.attachment)?; - Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment)) + let attachments = if self.attachment.is_empty() { + NoteAttachments::empty() + } else { + NoteAttachments::read_from_bytes(&self.attachment)? + }; + let partial = PartialNoteMetadata::new(sender, note_type).with_tag(tag); + let metadata = NoteMetadata::new(partial, &attachments); + Ok((metadata, attachments)) } } @@ -932,16 +940,14 @@ pub struct NoteInsertRow { impl From<(NoteRecord, Option)> for NoteInsertRow { fn from((note, nullifier): (NoteRecord, Option)) -> Self { - let attachment = note.metadata.attachment(); - - let target_account_id = NetworkAccountTarget::try_from(attachment).ok(); + let target_account_id = NetworkAccountTarget::try_from(¬e.attachments).ok(); let network_note_type = if target_account_id.is_some() && !note.metadata.is_private() { NetworkNoteType::SingleTarget } else { NetworkNoteType::None }; - let attachment_bytes = attachment.to_bytes(); + let attachment_bytes = note.attachments.to_bytes(); Self { committed_at: note.block_num.to_raw_sql(), diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 7cf672018..514206855 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -45,6 +45,7 @@ use miden_protocol::crypto::rand::RandomCoin; use miden_protocol::note::{ Note, NoteAttachment, + NoteAttachments, NoteDetails, NoteHeader, NoteId, @@ -52,6 +53,7 @@ use miden_protocol::note::{ NoteTag, NoteType, Nullifier, + PartialNoteMetadata, }; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PRIVATE_SENDER, @@ -233,7 +235,7 @@ pub fn create_note(account_id: AccountId) -> Note { FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(), 10).unwrap(), )], NoteType::Public, - NoteAttachment::default(), + NoteAttachments::empty(), &mut *rng, ) .expect("Failed to create note") @@ -265,8 +267,9 @@ fn sql_select_notes() { note_index: BlockNoteIndex::new(0, i.try_into().unwrap()).unwrap(), note_id: num_to_word(u64::try_from(i).unwrap()), note_commitment: num_to_word(u64::try_from(i).unwrap()), - metadata: new_note.metadata().clone(), + metadata: *new_note.metadata(), details: Some(NoteDetails::from(&new_note)), + attachments: new_note.attachments().clone(), inclusion_path: SparseMerklePath::default(), }; state.push(note.clone()); @@ -309,8 +312,9 @@ fn sql_select_note_script_by_root() { note_index: BlockNoteIndex::new(0, 0.try_into().unwrap()).unwrap(), note_id: num_to_word(0), note_commitment: num_to_word(0), - metadata: new_note.metadata().clone(), + metadata: *new_note.metadata(), details: Some(NoteDetails::from(&new_note)), + attachments: new_note.attachments().clone(), inclusion_path: SparseMerklePath::default(), }; state.push(note.clone()); @@ -380,14 +384,19 @@ fn sql_unconsumed_network_notes() { // Create an unconsumed note in each block. let notes = Vec::from_iter((0..2).map(|i: u32| { + let attachments = NoteAttachments::from(attachment.clone()); + let metadata = NoteMetadata::new( + PartialNoteMetadata::new(account_note.0, NoteType::Public), + &attachments, + ); let note = NoteRecord { block_num: 0.into(), // Created on same block. note_index: BlockNoteIndex::new(0, i as usize).unwrap(), note_id: num_to_word(i.into()), note_commitment: num_to_word(i.into()), - metadata: NoteMetadata::new(account_note.0, NoteType::Public) - .with_attachment(attachment.clone()), + metadata, details: None, + attachments, inclusion_path: SparseMerklePath::default(), }; (note, Some(num_to_nullifier(i.into()))) @@ -457,7 +466,7 @@ fn sql_select_accounts() { for i in 0..10u8 { let account_id = AccountId::dummy( [i; 15], - AccountIdVersion::Version0, + AccountIdVersion::Version1, AccountType::RegularAccountImmutableCode, AccountStorageMode::Private, ); @@ -854,9 +863,13 @@ fn notes() { let new_note = create_note(sender); let note_index = BlockNoteIndex::new(0, 2).unwrap(); let tag = 5u32; - let note_metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()); + let note_metadata = NoteMetadata::new( + PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()), + &NoteAttachments::default(), + ); - let values = [(note_index, new_note.id(), ¬e_metadata)]; + let note_header = NoteHeader::new(new_note.id(), note_metadata); + let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -865,8 +878,9 @@ fn notes() { note_index, note_id: new_note.id().as_word(), note_commitment: new_note.commitment(), - metadata: NoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()), + metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), + attachments: NoteAttachments::default(), inclusion_path: inclusion_path.clone(), }; @@ -895,8 +909,9 @@ fn notes() { note_index: note.note_index, note_id: new_note.id().as_word(), note_commitment: new_note.commitment(), - metadata: note.metadata.clone(), + metadata: note.metadata, details: None, + attachments: NoteAttachments::default(), inclusion_path: inclusion_path.clone(), }; @@ -955,8 +970,12 @@ fn note_sync_across_multiple_blocks() { .unwrap(); let new_note = create_note(sender); - let note_metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()); - let values = [(note_index, new_note.id(), ¬e_metadata)]; + let note_metadata = NoteMetadata::new( + PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()), + &NoteAttachments::default(), + ); + let note_header = NoteHeader::new(new_note.id(), note_metadata); + let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -967,6 +986,7 @@ fn note_sync_across_multiple_blocks() { note_commitment: new_note.commitment(), metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), + attachments: NoteAttachments::default(), inclusion_path, }; queries::insert_scripts(conn, [¬e]).unwrap(); @@ -1032,8 +1052,12 @@ fn note_sync_multi_respects_payload_limit() { .unwrap(); let new_note = create_note(sender); - let note_metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()); - let values = [(note_index, new_note.id(), ¬e_metadata)]; + let note_metadata = NoteMetadata::new( + PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()), + &NoteAttachments::default(), + ); + let note_header = NoteHeader::new(new_note.id(), note_metadata); + let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -1044,6 +1068,7 @@ fn note_sync_multi_respects_payload_limit() { note_commitment: new_note.commitment(), metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), + attachments: NoteAttachments::default(), inclusion_path, }; queries::insert_scripts(conn, [¬e]).unwrap(); @@ -1085,8 +1110,12 @@ fn note_sync_no_matching_tags() { // Insert a note with tag 10. let new_note = create_note(sender); let note_index = BlockNoteIndex::new(0, 0).unwrap(); - let note_metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(10u32.into()); - let values = [(note_index, new_note.id(), ¬e_metadata)]; + let note_metadata = NoteMetadata::new( + PartialNoteMetadata::new(sender, NoteType::Public).with_tag(10u32.into()), + &NoteAttachments::default(), + ); + let note_header = NoteHeader::new(new_note.id(), note_metadata); + let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -1097,6 +1126,7 @@ fn note_sync_no_matching_tags() { note_commitment: new_note.commitment(), metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), + attachments: NoteAttachments::default(), inclusion_path, }; queries::insert_scripts(conn, [¬e]).unwrap(); @@ -1753,7 +1783,11 @@ fn mock_block_transaction(account_id: AccountId, num: u64) -> TransactionHeader Word::try_from([num, num, 0, 0]).unwrap(), Word::try_from([0, 0, num, num]).unwrap(), ), - NoteMetadata::new(account_id, NoteType::Public).with_tag(NoteTag::new(num as u32)), + NoteMetadata::new( + PartialNoteMetadata::new(account_id, NoteType::Public) + .with_tag(NoteTag::new(num as u32)), + &NoteAttachments::default(), + ), )]; let fee = test_fee(); @@ -2377,7 +2411,10 @@ fn serialization_symmetry_note_metadata() { // Use a tag that roundtrips properly - NoteTag::LocalAny stores the full u32 including type // bits let tag = NoteTag::with_account_target(sender); - let metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(tag); + let metadata = NoteMetadata::new( + PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag), + &NoteAttachments::default(), + ); let bytes = metadata.to_bytes(); let restored = NoteMetadata::read_from_bytes(&bytes).unwrap(); @@ -2522,8 +2559,9 @@ fn db_roundtrip_notes() { note_index, note_id: new_note.id().as_word(), note_commitment: new_note.commitment(), - metadata: new_note.metadata().clone(), + metadata: *new_note.metadata(), details: Some(NoteDetails::from(&new_note)), + attachments: new_note.attachments().clone(), inclusion_path: SparseMerklePath::default(), }; @@ -2771,16 +2809,18 @@ fn db_roundtrip_note_metadata_attachment() { let attachment: NoteAttachment = target.into(); // Create NoteMetadata with the attachment + let attachments = NoteAttachments::from(attachment.clone()); let metadata = - NoteMetadata::new(account_id, NoteType::Public).with_attachment(attachment.clone()); + NoteMetadata::new(PartialNoteMetadata::new(account_id, NoteType::Public), &attachments); let note = NoteRecord { block_num, note_index: BlockNoteIndex::new(0, 0).unwrap(), note_id: num_to_word(1), note_commitment: num_to_word(1), - metadata: metadata.clone(), + metadata, details: None, + attachments: attachments.clone(), inclusion_path: SparseMerklePath::default(), }; @@ -2793,15 +2833,14 @@ fn db_roundtrip_note_metadata_attachment() { assert_eq!(retrieved.len(), 1, "Should retrieve exactly one note"); - let retrieved_metadata = &retrieved[0].metadata; + let retrieved_attachments = &retrieved[0].attachments; assert_eq!( - retrieved_metadata.attachment(), - metadata.attachment(), - "Attachment should be preserved after DB roundtrip" + retrieved_attachments, &attachments, + "Attachments should be preserved after DB roundtrip" ); - let retrieved_target = NetworkAccountTarget::try_from(retrieved_metadata.attachment()) - .expect("Should be able to parse NetworkAccountTarget from retrieved attachment"); + let retrieved_target = NetworkAccountTarget::try_from(retrieved_attachments) + .expect("Should be able to parse NetworkAccountTarget from retrieved attachments"); assert_eq!( retrieved_target.target_id(), account_id, @@ -3616,8 +3655,9 @@ fn db_roundtrip_transactions() { note_index: BlockNoteIndex::new(0, idx).unwrap(), note_id: note.id().as_word(), note_commitment: note.to_commitment(), - metadata: note.metadata().clone(), + metadata: *note.metadata(), details: None, + attachments: NoteAttachments::default(), inclusion_path: SparseMerklePath::default(), }, None, @@ -3640,7 +3680,7 @@ fn db_roundtrip_transactions() { block_num, note_index: BlockNoteIndex::new(0, idx).unwrap(), note_id: note.id().as_word(), - metadata: note.metadata().clone(), + metadata: *note.metadata(), inclusion_path: SparseMerklePath::default(), }) .collect(); @@ -3665,7 +3705,7 @@ fn db_roundtrip_transactions() { initial_state_commitment: Some(tx.initial_state_commitment().into()), final_state_commitment: Some(tx.final_state_commitment().into()), input_notes: tx.input_notes().iter().cloned().map(Into::into).collect(), - output_notes: tx.output_notes().iter().cloned().map(Into::into).collect(), + output_notes: tx.output_notes().iter().copied().map(Into::into).collect(), fee: Some(Asset::from(tx.fee()).into()), }), output_note_proofs: expected_sync_records diff --git a/crates/store/src/genesis/config/mod.rs b/crates/store/src/genesis/config/mod.rs index 5b14c651d..391c059e2 100644 --- a/crates/store/src/genesis/config/mod.rs +++ b/crates/store/src/genesis/config/mod.rs @@ -20,16 +20,15 @@ use miden_protocol::account::{ FungibleAssetDelta, NonFungibleAssetDelta, }; -use miden_protocol::asset::{FungibleAsset, TokenSymbol}; +use miden_protocol::asset::{AssetAmount, FungibleAsset, TokenSymbol}; use miden_protocol::block::FeeParameters; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::PublicKey; use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey as RpoSecretKey; use miden_protocol::errors::TokenSymbolError; -use miden_protocol::{Felt, ONE}; +use miden_protocol::{Felt, ONE, Word}; use miden_standards::AuthMethod; use miden_standards::account::auth::AuthSingleSig; -use miden_standards::account::faucets::{BasicFungibleFaucet, TokenMetadata}; -use miden_standards::account::metadata::{FungibleTokenMetadata, TokenName}; +use miden_standards::account::faucets::{FungibleFaucet, TokenName}; use miden_standards::account::policies::{ BurnPolicyConfig, MintPolicyConfig, @@ -288,11 +287,24 @@ impl GenesisConfig { let mut storage_delta = AccountStorageDelta::default(); if total_issuance != 0 { - let current_metadata = TokenMetadata::try_from(faucet_account.storage())?; - let updated_metadata = - current_metadata.with_token_supply(Felt::new(total_issuance))?; + let current_faucet = FungibleFaucet::try_from(faucet_account.storage())?; + let new_token_supply = AssetAmount::new(total_issuance)?; + let max_supply = current_faucet.max_supply().as_canonical_u64(); + if max_supply < total_issuance { + return Err(GenesisConfigError::MaxIssuanceExceeded { + max_supply, + symbol: symbol.clone(), + total_issuance, + }); + } + let new_token_config = Word::new([ + Felt::from(new_token_supply), + current_faucet.max_supply(), + Felt::from(current_faucet.decimals()), + Felt::from(current_faucet.symbol()), + ]); storage_delta - .set_item(TokenMetadata::metadata_slot().clone(), updated_metadata.into())?; + .set_item(FungibleFaucet::token_config_slot().clone(), new_token_config)?; tracing::debug!( "Reducing faucet account {faucet} for {symbol} by {amount}", faucet = faucet_id.to_hex(), @@ -317,8 +329,8 @@ impl GenesisConfig { debug_assert_eq!(faucet_account.nonce(), ONE); // sanity check the total issuance against - let metadata = TokenMetadata::try_from(faucet_account.storage())?; - let max_supply = metadata.max_supply().as_canonical_u64(); + let faucet = FungibleFaucet::try_from(faucet_account.storage())?; + let max_supply = faucet.max_supply().as_canonical_u64(); if max_supply < total_issuance { return Err(GenesisConfigError::MaxIssuanceExceeded { max_supply, @@ -400,9 +412,9 @@ impl NativeFaucetConfig { return Err(GenesisConfigError::NativeFaucetNotFungible { path: full_path }); } - let metadata = TokenMetadata::try_from(account.storage()) + let faucet = FungibleFaucet::try_from(account.storage()) .expect("validated as fungible faucet above"); - let symbol = TokenSymbolStr::from(metadata.symbol().clone()); + let symbol = TokenSymbolStr::from(faucet.symbol().clone()); Ok((account, symbol, None)) }, } @@ -442,22 +454,22 @@ impl FungibleFaucetConfig { AuthSingleSig::new(secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2); let init_seed: [u8; 32] = rng.random(); - let token_metadata = FungibleTokenMetadata::builder( - TokenName::new(&symbol.to_string()) - .expect("token symbol fits within token name byte limit"), - symbol.as_ref().clone(), - decimals, - max_supply, - ) - .build()?; + let faucet = FungibleFaucet::builder() + .name( + TokenName::new(&symbol.to_string()) + .expect("token symbol fits within token name byte limit"), + ) + .symbol(symbol.as_ref().clone()) + .decimals(decimals) + .max_supply(AssetAmount::new(max_supply)?) + .build()?; // It's similar to `fn create_basic_fungible_faucet`, but we need to cover more cases. let faucet_account = AccountBuilder::new(init_seed) .account_type(AccountType::FungibleFaucet) .storage_mode(storage_mode.into()) .with_auth_component(auth) - .with_component(token_metadata) - .with_component(BasicFungibleFaucet) + .with_component(faucet) .with_components(TokenPolicyManager::new( PolicyAuthority::AuthControlled, MintPolicyConfig::AllowAll, diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac index 2753dd76d..6ee9d158a 100644 Binary files a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac and b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac differ diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac index 9f7a0a2cb..5631e6629 100644 Binary files a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac and b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac differ diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac b/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac index 607ba4c8c..bb22ae374 100644 Binary files a/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac and b/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac differ diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index 3ad58f003..743523186 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -45,11 +45,11 @@ fn parsing_yields_expected_default_values() -> TestResult { assert_eq!(wallet2.nonce(), ONE); { - let metadata = TokenMetadata::try_from(native_faucet.storage()).unwrap(); + let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap(); - assert_eq!(metadata.max_supply(), Felt::new(100_000_000_000_000_000)); - assert_eq!(metadata.decimals(), 6); - assert_eq!(*metadata.symbol(), TokenSymbol::new("MIDEN").unwrap()); + assert_eq!(faucet.max_supply(), Felt::new(100_000_000_000_000_000)); + assert_eq!(faucet.decimals(), 6); + assert_eq!(*faucet.symbol(), TokenSymbol::new("MIDEN").unwrap()); } // check account balance, and ensure ordering is retained @@ -61,8 +61,8 @@ fn parsing_yields_expected_default_values() -> TestResult { }); // check total issuance of the faucet - let metadata = TokenMetadata::try_from(native_faucet.storage()).unwrap(); - assert_eq!(metadata.token_supply(), Felt::new(999_777), "Issuance mismatch"); + let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap(); + assert_eq!(faucet.token_supply(), Felt::new(999_777), "Issuance mismatch"); Ok(()) } @@ -147,8 +147,8 @@ path = "test_account.mac" fn parsing_native_faucet_from_file() -> TestResult { use miden_protocol::account::auth::AuthScheme; use miden_protocol::account::{AccountBuilder, AccountFile, AccountStorageMode, AccountType}; + use miden_protocol::asset::AssetAmount; use miden_standards::account::auth::AuthSingleSig; - use miden_standards::account::metadata::{FungibleTokenMetadata, TokenName}; use miden_standards::account::policies::{ BurnPolicyConfig, MintPolicyConfig, @@ -169,20 +169,18 @@ fn parsing_native_faucet_from_file() -> TestResult { ); let auth = AuthSingleSig::new(secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2); - let token_metadata = FungibleTokenMetadata::builder( - TokenName::new("MIDEN").unwrap(), - TokenSymbol::new("MIDEN").unwrap(), - 6, - 1_000_000_000, - ) - .build()?; + let faucet = FungibleFaucet::builder() + .name(TokenName::new("MIDEN").unwrap()) + .symbol(TokenSymbol::new("MIDEN").unwrap()) + .decimals(6) + .max_supply(AssetAmount::new(1_000_000_000)?) + .build()?; let faucet_account = AccountBuilder::new(init_seed) .account_type(AccountType::FungibleFaucet) .storage_mode(AccountStorageMode::Public) .with_auth_component(auth) - .with_component(token_metadata) - .with_component(BasicFungibleFaucet) + .with_component(faucet) .with_components(TokenPolicyManager::new( PolicyAuthority::AuthControlled, MintPolicyConfig::AllowAll, @@ -350,8 +348,8 @@ async fn parsing_agglayer_sample_with_account_files() -> TestResult { // Verify native faucet symbol { - let metadata = TokenMetadata::try_from(native_faucet.storage()).unwrap(); - assert_eq!(*metadata.symbol(), TokenSymbol::new("MIDEN").unwrap()); + let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap(); + assert_eq!(*faucet.symbol(), TokenSymbol::new("MIDEN").unwrap()); } // Bridge account is a regular account (not a faucet) diff --git a/crates/store/src/server/ntx_builder.rs b/crates/store/src/server/ntx_builder.rs index e0cc425c9..e947eae71 100644 --- a/crates/store/src/server/ntx_builder.rs +++ b/crates/store/src/server/ntx_builder.rs @@ -116,7 +116,9 @@ impl ntx_builder_server::NtxBuilder for StoreApi { // SAFETY: Network notes are filtered in the database, so they should have details; // otherwise the state would be corrupted let (assets, recipient) = note.details.unwrap().into_parts(); - let note = Note::new(assets, note.metadata, recipient); + let partial_metadata = *note.metadata.partial_metadata(); + let note = + Note::with_attachments(assets, partial_metadata, recipient, note.attachments); network_notes.push(note.into()); } diff --git a/crates/store/src/state/apply_block.rs b/crates/store/src/state/apply_block.rs index c3cc48826..8cfb396f7 100644 --- a/crates/store/src/state/apply_block.rs +++ b/crates/store/src/state/apply_block.rs @@ -7,7 +7,7 @@ use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::block::account_tree::AccountMutationSet; use miden_protocol::block::nullifier_tree::NullifierMutationSet; use miden_protocol::block::{BlockBody, BlockHeader, SignedBlock}; -use miden_protocol::note::{NoteDetails, Nullifier}; +use miden_protocol::note::{NoteAttachments, NoteDetails, Nullifier}; use miden_protocol::transaction::OutputNote; use miden_protocol::utils::serde::Serializable; use tokio::sync::oneshot; @@ -327,11 +327,13 @@ impl State { let notes = body .output_notes() .map(|(note_index, note)| { - let (details, nullifier) = match note { - OutputNote::Public(note) => { - (Some(NoteDetails::from(note.as_note())), Some(note.as_note().nullifier())) - }, - OutputNote::Private(_) => (None, None), + let (details, attachments, nullifier) = match note { + OutputNote::Public(public) => ( + Some(NoteDetails::from(public.as_note())), + public.as_note().attachments().clone(), + Some(public.as_note().nullifier()), + ), + OutputNote::Private(_) => (None, NoteAttachments::empty(), None), }; let inclusion_path = note_tree.open(note_index); @@ -341,8 +343,9 @@ impl State { note_index, note_id: note.id().as_word(), note_commitment: note.to_commitment(), - metadata: note.metadata().clone(), + metadata: *note.metadata(), details, + attachments, inclusion_path, }; diff --git a/proto/proto/types/note.proto b/proto/proto/types/note.proto index 8b365b152..78c890cea 100644 --- a/proto/proto/types/note.proto +++ b/proto/proto/types/note.proto @@ -17,18 +17,6 @@ enum NoteType { NOTE_TYPE_PRIVATE = 2; } -// The kind of a note attachment. -enum NoteAttachmentKind { - // Unspecified attachment kind (default value, should not be used). - NOTE_ATTACHMENT_KIND_UNSPECIFIED = 0; - // No attachment. - NOTE_ATTACHMENT_KIND_NONE = 1; - // A single word attachment. - NOTE_ATTACHMENT_KIND_WORD = 2; - // An array attachment. - NOTE_ATTACHMENT_KIND_ARRAY = 3; -} - // Represents a note's ID. message NoteId { // A unique identifier of the note which is a 32-byte commitment to the underlying note data. @@ -41,30 +29,12 @@ message NoteIdList { repeated NoteId ids = 1; } -// Represents the header of a note's metadata. -// -// Contains the sender, note type, tag and attachment type information, but not the full -// attachment data. See `miden_protocol::note::NoteMetadataHeader` for more info. -message NoteMetadataHeader { - // The account which sent the note. - account.AccountId sender = 1; - - // The type of the note. - NoteType note_type = 2; - - // A value which can be used by the recipient to identify notes intended for them. - // - // See `miden_protocol::note::note_tag` for more info. - fixed32 tag = 3; - - // The kind of the note attachment. - NoteAttachmentKind attachment_kind = 4; - - // The scheme identifier of the note attachment. - fixed32 attachment_scheme = 5; -} - // Represents a note's metadata. +// +// Mirrors the protocol-level `miden_protocol::note::NoteMetadata`: it carries the partial metadata +// (sender, note type, tag), the per-slot attachment schemes, and the commitment over the note's +// attachments. The full attachment contents (when present) are carried separately on `Note` / +// `NetworkNote`. message NoteMetadata { // The account which sent the note. account.AccountId sender = 1; @@ -77,21 +47,33 @@ message NoteMetadata { // See `miden_protocol::note::note_tag` for more info. fixed32 tag = 3; - // Serialized note attachment + // The attachment scheme of each attachment slot. + // + // The protocol allows up to `NoteAttachments::MAX_COUNT` attachments per note. Each slot carries + // either a scheme value in `1..=65534` or `0` to indicate that the slot is absent. Trailing + // absent slots may be omitted, so a note with no attachments has an empty list. // - // See `miden_protocol::note::NoteAttachment` for more info. - bytes attachment = 4; + // See `miden_protocol::note::NoteAttachmentHeader` for more info. + repeated fixed32 attachment_schemes = 4; + + // Commitment over the note's attachments. + // + // See `miden_protocol::note::NoteAttachments::to_commitment` for more info. + primitives.Digest attachments_commitment = 5; } // Represents a note. // -// The note is composed of the note metadata and its serialized details. +// The note is composed of the note metadata, its serialized details, and serialized attachments. message Note { // The note's metadata. NoteMetadata metadata = 1; // Serialized note details (empty for private notes). optional bytes details = 2; + + // Serialized `miden_protocol::note::NoteAttachments`. Empty bytes encode an empty collection. + bytes attachments = 3; } // Represents a network note. @@ -104,6 +86,9 @@ message NetworkNote { // Serialized note details (i.e., assets and recipient). bytes details = 2; + + // Serialized `miden_protocol::note::NoteAttachments`. Empty bytes encode an empty collection. + bytes attachments = 3; } // Represents a committed note. @@ -140,12 +125,12 @@ message NoteInclusionInBlockProof { primitives.SparseMerklePath inclusion_path = 4; } -// Represents a note's metadata header together with proof of inclusion in a block. +// Represents a note's metadata together with proof of inclusion in a block. // -// To get the full `NoteMetadata` (including attachment), use `GetNotesById`. +// To get the full note (including attachment contents), use `GetNotesById`. message NoteSyncRecord { - // The fixed-size metadata header of the note. - NoteMetadataHeader metadata_header = 1; + // The metadata of the note. + NoteMetadata metadata = 1; // Proof of the note's inclusion in a block. NoteInclusionInBlockProof inclusion_proof = 2;