feat: add .is_network_note() helper method for Note#2365
feat: add .is_network_note() helper method for Note#2365partylikeits1983 wants to merge 9 commits intonextfrom
.is_network_note() helper method for Note#2365Conversation
3b1b042 to
c1aea73
Compare
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.
There was a problem hiding this comment.
Agree with copilot.
|
|
||
| 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
PhilippGackstatter
left a comment
There was a problem hiding this comment.
Looks good to me! Sorry for the delay!
Left a small comment re usability of this type in the node. I think copilot makes some good points as well.
| pub struct AccountTargetNetworkNote<'a> { | ||
| note: &'a Note, |
There was a problem hiding this comment.
I'd probably make this own the underlying note and provide unwrap functionality like AccountTargetNetworkNote::into_note(self) -> Note and AccountTargetNetworkNote::as_note(&self) -> &Note. NetworkNoteExt::as_account_target_network_note would become into_*.
I think without this the node would probably not be able to use the wrapper (and maybe that's fine?) cc @Mirko-von-Leipzig @sergerad .
There was a problem hiding this comment.
Agreed - I think wrapping a full note (rather than just a reference) would make this more flexible. AFAIK, it most relevant contexts, we'd be converting a Note into AccountTargetNetworkNote. But also, if that's not the case, cloning here wouldn't be too bad either (and if we do want to avoid cloning, using Arc could be a better option).
| /// 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.
Agree with copilot.
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I left a couple of comments inline.
| pub struct AccountTargetNetworkNote<'a> { | ||
| note: &'a Note, |
There was a problem hiding this comment.
Agreed - I think wrapping a full note (rather than just a reference) would make this more flexible. AFAIK, it most relevant contexts, we'd be converting a Note into AccountTargetNetworkNote. But also, if that's not the case, cloning here wouldn't be too bad either (and if we do want to avoid cloning, using Arc could be a better option).
| /// 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> { |
There was a problem hiding this comment.
I don't love the name AccountTargetNetworkNote, but also don't have much better suggestions. Maybe something like AccountBoundNetworkNote?
This PR adds a new
NetworkNotetype which makes it easy to determine if aNoteis a network note or not.Resolves: #2362