feat: add .is_network_note() helper method for Note#2365
feat: add .is_network_note() helper method for Note#2365partylikeits1983 wants to merge 8 commits intonextfrom
.is_network_note() helper method for Note#2365Conversation
3b1b042 to
c1aea73
Compare
| /// A view over a [`Note`] that is guaranteed to target a network account via a | ||
| /// [`NetworkAccountTarget`] attachment. | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct NetworkNote<'a> { | ||
| note: &'a Note, | ||
| target: NetworkAccountTarget, | ||
| } |
There was a problem hiding this comment.
This basically defines "network note = note targeted at a network account". This is fine, but the definition may also be too narrow. E.g. in the future network notes could also include SWAP notes that are not targeted but can still be consumed by network accounts. So, I think it would be good to reflect this in the name, e.g. NetworkAccountTargetNote or AccountTargetNetworkNote. The node terms this SingleTargetNetworkNote which may also be fine.
There was a problem hiding this comment.
this is a good idea, however for now, I kept NetworkNote for API simplicity since we don't have these multi-targeted network notes just yet. I added docs clarifying it's specifically for account-targeted notes. We can introduce more specific types later if needed for SWAP notes or other network note variants. What do you think, can we keep NetworkNote as is, or should we add the AccountTargetNetworkNote as you suggested now, or leave this for a later PR?
My goal with this PR is just to have a super simple method for seeing if a given Note is a network note or not.
Like this:
let is_network_note: bool = note.is_network_note();There was a problem hiding this comment.
My goal with this PR is just to have a super simple method for seeing if a given
Noteis a network note or not.
Yes, that's completely fair, and I don't want to make it more complicated than it needs to be. As we're nearing a 1.0 release, in my mind, and I could be misguided, it's worth thinking about whether this structure will still be accurate in 6-12 months when we can't do a refactor of this type anymore and when we may have introduced the concept of a "multi-target network note". Then, having NetworkNote (representing singel target notes) and MultiTargetNetworkNote will be an inconsistent API. Because of that, I still think the NetworkNote type is named too generically, since it does not and cannot represent any network note, just "single target" ones.
There was a problem hiding this comment.
Makes sense! I updated the naming to AccountTargetNetworkNote
There was a problem hiding this comment.
Pull request overview
This PR introduces a new AccountTargetNetworkNote type and NetworkNoteExt trait to make it easier to determine if a Note is a network note and to access network-specific properties. This addresses issue #2362 by providing a cleaner, more discoverable API compared to the previous approach of using NetworkAccountTarget::try_from(note.metadata().attachment()).
Changes:
- Added
AccountTargetNetworkNote<'a>struct providing a view over notes withNetworkAccountTargetattachments - Added
NetworkNoteExttrait withis_network_note()andas_account_target_network_note()convenience methods - Added integration test
test_network_note_unwrap()verifying the new API works correctly
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| crates/miden-standards/src/note/network_note.rs | New module defining AccountTargetNetworkNote struct and NetworkNoteExt trait for working with network notes |
| crates/miden-standards/src/note/mod.rs | Exports the new AccountTargetNetworkNote and NetworkNoteExt types |
| crates/miden-testing/src/kernel_tests/tx/test_output_note.rs | Adds integration test verifying the new network note API |
| CHANGELOG.md | Documents the addition of the NetworkNote type in release 0.14.0 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Returns the target network [`AccountId`]. | ||
| pub fn target_account_id(&self) -> AccountId { | ||
| self.target().target_id() | ||
| } | ||
|
|
||
| /// Returns the decoded [`NetworkAccountTarget`] attachment. | ||
| pub fn target(&self) -> NetworkAccountTarget { | ||
| NetworkAccountTarget::try_from(self.note.metadata().attachment()) | ||
| .expect("AccountTargetNetworkNote guarantees valid NetworkAccountTarget attachment") | ||
| } | ||
|
|
||
| /// Returns the raw [`NoteAttachment`] from the note metadata. | ||
| pub fn attachment(&self) -> &NoteAttachment { | ||
| self.metadata().attachment() | ||
| } | ||
|
|
||
| /// Returns the [`NoteType`] of the underlying note. | ||
| pub fn note_type(&self) -> NoteType { | ||
| self.metadata().note_type() | ||
| } |
There was a problem hiding this comment.
Consider adding an execution_hint() accessor method to AccountTargetNetworkNote for consistency and convenience. While users can access it via network_note.target().execution_hint(), having a direct accessor like network_note.execution_hint() would match the pattern established with target_account_id() and provide a more convenient API. The NetworkAccountTarget already has an execution_hint() method that could be exposed here.
|
|
||
| Ok(()) | ||
| } | ||
|
|
There was a problem hiding this comment.
The test test_network_note_unwrap only verifies the positive case where a note has a valid NetworkAccountTarget attachment. Consider also testing the negative cases where:
- A note without a
NetworkAccountTargetattachment (e.g., a regular P2ID note) returnsfalseforis_network_note() as_account_target_network_note()returns an appropriate error for non-network notes
This would provide more comprehensive coverage of the new API and ensure it correctly distinguishes between network and non-network notes.
| #[tokio::test] | |
| async fn test_non_network_note_is_not_marked_as_network() -> anyhow::Result<()> { | |
| let sender = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); | |
| let mut rng = RpoRandomCoin::new(Word::from([4, 3, 2, 1u32])); | |
| // Build a regular note without a NetworkAccountTarget attachment. | |
| let note = NoteBuilder::new(sender.id(), &mut rng) | |
| .note_type(NoteType::Public) | |
| .build()?; | |
| assert!(!note.is_network_note()); | |
| Ok(()) | |
| } | |
| #[tokio::test] | |
| async fn test_non_network_note_unwrap_returns_error() -> anyhow::Result<()> { | |
| let sender = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); | |
| let mut rng = RpoRandomCoin::new(Word::from([5, 6, 7, 8u32])); | |
| // Build a regular note without a NetworkAccountTarget attachment. | |
| let note = NoteBuilder::new(sender.id(), &mut rng) | |
| .note_type(NoteType::Public) | |
| .build()?; | |
| let result = note.as_account_target_network_note(); | |
| assert!(result.is_err()); | |
| Ok(()) | |
| } |
| use miden_protocol::account::AccountId; | ||
| use miden_protocol::note::{Note, NoteAttachment, NoteMetadata, NoteType}; | ||
|
|
||
| use crate::note::{NetworkAccountTarget, NetworkAccountTargetError}; | ||
|
|
||
| /// A view over a [`Note`] that is guaranteed to target a network account via a | ||
| /// [`NetworkAccountTarget`] attachment. | ||
| /// | ||
| /// This represents a note that is specifically targeted at a single network account. In the future, | ||
| /// other types of network notes may exist (e.g., SWAP notes that can be consumed by network | ||
| /// accounts but are not targeted at a specific one). | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct AccountTargetNetworkNote<'a> { | ||
| note: &'a Note, | ||
| } | ||
|
|
||
| impl<'a> AccountTargetNetworkNote<'a> { | ||
| /// Attempts to construct an [`AccountTargetNetworkNote`] view over `note`. | ||
| /// | ||
| /// Returns an error if the note's attachment cannot be decoded as a [`NetworkAccountTarget`]. | ||
| pub fn new(note: &'a Note) -> Result<Self, NetworkAccountTargetError> { | ||
| // Validate that the attachment is a valid NetworkAccountTarget | ||
| NetworkAccountTarget::try_from(note.metadata().attachment())?; | ||
| Ok(Self { note }) | ||
| } | ||
|
|
||
| /// Returns the underlying [`Note`]. | ||
| pub fn as_note(&self) -> &'a Note { | ||
| self.note | ||
| } | ||
|
|
||
| /// Returns the [`NoteMetadata`] of the underlying note. | ||
| pub fn metadata(&self) -> &NoteMetadata { | ||
| self.note.metadata() | ||
| } | ||
|
|
||
| /// Returns the target network [`AccountId`]. | ||
| pub fn target_account_id(&self) -> AccountId { | ||
| self.target().target_id() | ||
| } | ||
|
|
||
| /// Returns the decoded [`NetworkAccountTarget`] attachment. | ||
| pub fn target(&self) -> NetworkAccountTarget { | ||
| NetworkAccountTarget::try_from(self.note.metadata().attachment()) | ||
| .expect("AccountTargetNetworkNote guarantees valid NetworkAccountTarget attachment") | ||
| } | ||
|
|
||
| /// Returns the raw [`NoteAttachment`] from the note metadata. | ||
| pub fn attachment(&self) -> &NoteAttachment { | ||
| self.metadata().attachment() | ||
| } | ||
|
|
||
| /// Returns the [`NoteType`] of the underlying note. | ||
| pub fn note_type(&self) -> NoteType { | ||
| self.metadata().note_type() | ||
| } | ||
| } | ||
|
|
||
| /// Convenience helpers for [`Note`]s that may target a network account. | ||
| pub trait NetworkNoteExt { | ||
| /// Returns `true` if this note's attachment decodes as a [`NetworkAccountTarget`]. | ||
| fn is_network_note(&self) -> bool; | ||
|
|
||
| /// Returns an [`AccountTargetNetworkNote`] view, or an error if the attachment is not a valid | ||
| /// target. | ||
| fn as_account_target_network_note( | ||
| &self, | ||
| ) -> Result<AccountTargetNetworkNote<'_>, NetworkAccountTargetError>; | ||
| } | ||
|
|
||
| impl NetworkNoteExt for Note { | ||
| fn is_network_note(&self) -> bool { | ||
| NetworkAccountTarget::try_from(self.metadata().attachment()).is_ok() | ||
| } | ||
|
|
||
| fn as_account_target_network_note( | ||
| &self, | ||
| ) -> Result<AccountTargetNetworkNote<'_>, NetworkAccountTargetError> { | ||
| AccountTargetNetworkNote::new(self) | ||
| } | ||
| } | ||
|
|
||
| impl<'a> TryFrom<&'a Note> for AccountTargetNetworkNote<'a> { | ||
| type Error = NetworkAccountTargetError; | ||
|
|
||
| fn try_from(note: &'a Note) -> Result<Self, Self::Error> { | ||
| Self::new(note) | ||
| } | ||
| } |
There was a problem hiding this comment.
The network_note.rs module lacks unit tests. Similar modules in this crate (such as network_account_target.rs, p2id.rs, p2ide.rs, and swap.rs) all include unit tests under a #[cfg(test)] section to validate their functionality. Consider adding unit tests that verify:
AccountTargetNetworkNote::new()succeeds when given a note with a validNetworkAccountTargetattachmentAccountTargetNetworkNote::new()fails when given a note with an invalid attachmentis_network_note()returnstruefor notes with validNetworkAccountTargetattachments andfalseotherwiseas_account_target_network_note()returns the correct result for both valid and invalid notes- The
TryFromimplementation works correctly
These tests would complement the integration test in test_output_note.rs and ensure the module behaves correctly in isolation.
NetworkNote newtype.is_network_note() helper method for Note
This PR adds a new
NetworkNotetype which makes it easy to determine if aNoteis a network note or not.Resolves: #2362