diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf050f30..76e48827f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ - Remove the cyclic database optimization ([#1497](https://github.com/0xMiden/miden-node/pull/1497)). - Fix race condition at DB shutdown in tests ([#1503](https://github.com/0xMiden/miden-node/pull/1503)). - [BREAKING] Updated to new miden-base protocol: removed `aux` and `execution_hint` from `NoteMetadata`, removed `NoteExecutionMode`, and `NoteMetadata::new()` is now infallible ([#1526](https://github.com/0xMiden/miden-node/pull/1526)). +- [BREAKING] Network note queries now use full account ID instead of 30-bit prefix ([#1572](https://github.com/0xMiden/miden-node/pull/1572)). ### Fixes diff --git a/crates/ntx-builder/src/store.rs b/crates/ntx-builder/src/store.rs index 1a7c7b309..02e12d896 100644 --- a/crates/ntx-builder/src/store.rs +++ b/crates/ntx-builder/src/store.rs @@ -156,7 +156,7 @@ impl StoreClient { let req = proto::store::UnconsumedNetworkNotesRequest { page_token, page_size: PAGE_SIZE, - network_account_id_prefix: network_account_id.prefix(), + account_id: Some(network_account_id.inner().into()), block_num, }; let resp = store_client.get_unconsumed_network_notes(req).await?.into_inner(); diff --git a/crates/proto/src/generated/store.rs b/crates/proto/src/generated/store.rs index 2fc8168fb..aad46a422 100644 --- a/crates/proto/src/generated/store.rs +++ b/crates/proto/src/generated/store.rs @@ -168,7 +168,7 @@ pub struct MaybeAccountDetails { /// Returns a paginated list of unconsumed network notes for an account. /// /// Notes created or consumed after the specified block are excluded from the result. -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct UnconsumedNetworkNotesRequest { /// This should be null on the first call, and set to the response token until the response token /// is null, at which point all data has been fetched. @@ -179,9 +179,9 @@ pub struct UnconsumedNetworkNotesRequest { /// Number of notes to retrieve per page. #[prost(uint64, tag = "2")] pub page_size: u64, - /// The network account ID prefix to filter notes by. - #[prost(uint32, tag = "3")] - pub network_account_id_prefix: u32, + /// The full account ID to filter notes by. + #[prost(message, optional, tag = "3")] + pub account_id: ::core::option::Option, /// The block number to filter the returned notes by. /// /// Notes that are created or consumed after this block are excluded from the result. diff --git a/crates/store/src/db/migrations/2025062000000_setup/up.sql b/crates/store/src/db/migrations/2025062000000_setup/up.sql index d3638a5be..b3ca25d56 100644 --- a/crates/store/src/db/migrations/2025062000000_setup/up.sql +++ b/crates/store/src/db/migrations/2025062000000_setup/up.sql @@ -52,6 +52,7 @@ CREATE TABLE notes ( sender BLOB NOT NULL, tag INTEGER NOT NULL, network_note_type INTEGER NOT NULL, -- 0-not a network note, 1-single account target network note + target_account_id BLOB, -- Full target account ID for single-target network notes attachment BLOB NOT NULL, -- Serialized note attachment data inclusion_path BLOB NOT NULL, -- Serialized sparse Merkle path of the note in the block's note tree consumed_at INTEGER, -- Block number when the note was consumed @@ -74,7 +75,7 @@ CREATE INDEX idx_notes_note_commitment ON notes(note_commitment); CREATE INDEX idx_notes_sender ON notes(sender, committed_at); CREATE INDEX idx_notes_tag ON notes(tag, committed_at); CREATE INDEX idx_notes_nullifier ON notes(nullifier); -CREATE INDEX idx_unconsumed_network_notes ON notes(network_note_type, consumed_at); +CREATE INDEX idx_notes_target_account ON notes(target_account_id, committed_at) WHERE target_account_id IS NOT NULL; -- Index for joining with block_headers on committed_at CREATE INDEX idx_notes_committed_at ON notes(committed_at); -- Index for joining with note_scripts diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 7ecfcb745..ee7c722c8 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -652,19 +652,13 @@ impl Db { /// Pagination is used to limit the number of notes returned. pub(crate) async fn select_unconsumed_network_notes( &self, - network_account_prefix: u32, + account_id: AccountId, block_num: BlockNumber, page: Page, ) -> Result<(Vec, Page)> { - // Single-target network notes have their tags derived from the target account ID. - // The 30-bit account ID prefix is used as the note tag, allowing us to query notes - // for a given network account. self.transact("unconsumed network notes for account", move |conn| { - models::queries::select_unconsumed_network_notes_by_tag( - conn, - network_account_prefix, - block_num, - page, + models::queries::select_unconsumed_network_notes_by_account_id( + conn, account_id, block_num, page, ) }) .await diff --git a/crates/store/src/db/models/queries/notes.rs b/crates/store/src/db/models/queries/notes.rs index 10b8316de..a2ab7b1bb 100644 --- a/crates/store/src/db/models/queries/notes.rs +++ b/crates/store/src/db/models/queries/notes.rs @@ -435,7 +435,7 @@ pub(crate) fn select_note_script_by_root( /// FROM notes /// LEFT JOIN note_scripts ON notes.script_root = note_scripts.script_root /// WHERE -/// network_note_type = 1 AND tag = ?1 AND +/// network_note_type = 1 AND target_account_id = ?1 AND /// committed_at <= ?2 AND /// (consumed_at IS NULL OR consumed_at > ?2) AND notes.rowid >= ?3 /// ORDER BY notes.rowid ASC @@ -449,9 +449,9 @@ pub(crate) fn select_note_script_by_root( clippy::too_many_lines, reason = "Lines will be reduced when schema is updated to simplify logic" )] -pub(crate) fn select_unconsumed_network_notes_by_tag( +pub(crate) fn select_unconsumed_network_notes_by_account_id( conn: &mut SqliteConnection, - tag: u32, + account_id: AccountId, block_num: BlockNumber, mut page: Page, ) -> Result<(Vec, Page), DatabaseError> { @@ -494,7 +494,7 @@ pub(crate) fn select_unconsumed_network_notes_by_tag( ), ) .filter(schema::notes::network_note_type.eq(i32::from(NetworkNoteType::SingleTarget))) - .filter(schema::notes::tag.eq(tag as i32)) + .filter(schema::notes::target_account_id.eq(Some(account_id.to_bytes()))) .filter(schema::notes::committed_at.le(block_num.to_raw_sql())) .filter( schema::notes::consumed_at @@ -861,22 +861,24 @@ pub struct NoteInsertRow { pub sender: Vec, // AccountId pub tag: i32, + pub network_note_type: i32, + pub target_account_id: Option>, pub attachment: Vec, + pub inclusion_path: Vec, pub consumed_at: Option, + pub nullifier: Option>, pub assets: Option>, pub inputs: Option>, - pub serial_num: Option>, - pub nullifier: Option>, pub script_root: Option>, - pub network_note_type: i32, - pub inclusion_path: Vec, + pub serial_num: Option>, } impl From<(NoteRecord, Option)> for NoteInsertRow { fn from((note, nullifier): (NoteRecord, Option)) -> Self { let attachment = note.metadata.attachment(); - let network_note_type = if NetworkAccountTarget::try_from(attachment).is_ok() { + let target_account_id = NetworkAccountTarget::try_from(attachment).ok(); + let network_note_type = if target_account_id.is_some() { NetworkNoteType::SingleTarget } else { NetworkNoteType::None @@ -894,6 +896,7 @@ impl From<(NoteRecord, Option)> for NoteInsertRow { sender: note.metadata.sender().to_bytes(), tag: note.metadata.tag().to_raw_sql(), network_note_type: network_note_type.into(), + target_account_id: target_account_id.map(|t| t.target_id().to_bytes()), attachment: attachment_bytes, inclusion_path: note.inclusion_path.to_bytes(), consumed_at: None::, // New notes are always unconsumed. diff --git a/crates/store/src/db/schema.rs b/crates/store/src/db/schema.rs index 8b6b7e832..013284892 100644 --- a/crates/store/src/db/schema.rs +++ b/crates/store/src/db/schema.rs @@ -68,6 +68,7 @@ diesel::table! { sender -> Binary, tag -> Integer, network_note_type -> Integer, + target_account_id -> Nullable, attachment -> Binary, inclusion_path -> Binary, consumed_at -> Nullable, diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 5d3785b45..6bd26dda1 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -393,9 +393,9 @@ fn sql_unconsumed_network_notes() { // Both notes are unconsumed, query should return both notes on both blocks. (0..2).for_each(|i: u32| { - let (result, _) = queries::select_unconsumed_network_notes_by_tag( + let (result, _) = queries::select_unconsumed_network_notes_by_account_id( &mut conn, - NoteTag::with_account_target(account_note.0).into(), + account_note.0, i.into(), Page { token: None, @@ -410,9 +410,9 @@ fn sql_unconsumed_network_notes() { queries::insert_nullifiers_for_block(&mut conn, &[notes[1].1.unwrap()], 1.into()).unwrap(); // Query against first block should return both notes. - let (result, _) = queries::select_unconsumed_network_notes_by_tag( + let (result, _) = queries::select_unconsumed_network_notes_by_account_id( &mut conn, - NoteTag::with_account_target(account_note.0).into(), + account_note.0, 0.into(), Page { token: None, @@ -423,9 +423,9 @@ fn sql_unconsumed_network_notes() { assert_eq!(result.len(), 2); // Query against second block should return only first note. - let (result, _) = queries::select_unconsumed_network_notes_by_tag( + let (result, _) = queries::select_unconsumed_network_notes_by_account_id( &mut conn, - NoteTag::with_account_target(account_note.0).into(), + account_note.0, 1.into(), Page { token: None, diff --git a/crates/store/src/server/ntx_builder.rs b/crates/store/src/server/ntx_builder.rs index 800eeedf6..f407ff861 100644 --- a/crates/store/src/server/ntx_builder.rs +++ b/crates/store/src/server/ntx_builder.rs @@ -97,10 +97,7 @@ impl ntx_builder_server::NtxBuilder for StoreApi { ) -> Result, Status> { let request = request.into_inner(); let block_num = BlockNumber::from(request.block_num); - let network_account_prefix = - validate_network_account_prefix(request.network_account_id_prefix).map_err(|err| { - invalid_argument(err.as_report_context("invalid network_account_id_prefix")) - })?; + let account_id = read_account_id::(request.account_id)?; let state = self.state.clone(); @@ -112,7 +109,7 @@ impl ntx_builder_server::NtxBuilder for StoreApi { // TODO: no need to get the whole NoteRecord here, a NetworkNote wrapper should be created // instead let (notes, next_page) = state - .get_unconsumed_network_notes_for_account(network_account_prefix, block_num, page) + .get_unconsumed_network_notes_for_account(account_id, block_num, page) .await .map_err(internal_error)?; diff --git a/crates/store/src/state/mod.rs b/crates/store/src/state/mod.rs index 85332ec2b..a3f4dbd77 100644 --- a/crates/store/src/state/mod.rs +++ b/crates/store/src/state/mod.rs @@ -1228,13 +1228,11 @@ impl State { /// along with the next pagination token. pub async fn get_unconsumed_network_notes_for_account( &self, - network_account_prefix: u32, + account_id: AccountId, block_num: BlockNumber, page: Page, ) -> Result<(Vec, Page), DatabaseError> { - self.db - .select_unconsumed_network_notes(network_account_prefix, block_num, page) - .await + self.db.select_unconsumed_network_notes(account_id, block_num, page).await } /// Returns the script for a note by its root. diff --git a/proto/proto/internal/store.proto b/proto/proto/internal/store.proto index 4ff8ac05d..9b5351367 100644 --- a/proto/proto/internal/store.proto +++ b/proto/proto/internal/store.proto @@ -308,8 +308,8 @@ message UnconsumedNetworkNotesRequest { // Number of notes to retrieve per page. uint64 page_size = 2; - // The network account ID prefix to filter notes by. - uint32 network_account_id_prefix = 3; + // The full account ID to filter notes by. + account.AccountId account_id = 3; // The block number to filter the returned notes by. //