Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
051de21
feat: comput emmr proof at chain tip for `sync_notes`
TomasArrachea Mar 25, 2026
6e0dd69
feat: return multiple blocks on `SyncNotes`
TomasArrachea Mar 25, 2026
cc7ce76
chore: format
TomasArrachea Mar 26, 2026
4ddb750
feat: update response size constants
TomasArrachea Mar 26, 2026
2d16d93
review: return requested block num in the response if specified
TomasArrachea Mar 26, 2026
adbefa6
feat: move sync notes loop to the Store (#1851)
TomasArrachea Mar 27, 2026
ff4a02b
Merge branch 'next' into tomasarrachea-sync-notes-rework
bobbinth Mar 27, 2026
736c937
review: fix `SyncNotesResponse` proto field numbering
TomasArrachea Mar 27, 2026
01823ca
Merge branch 'next' into tomasarrachea-sync-notes-rework
TomasArrachea Mar 27, 2026
cbb997a
review: remove comment on proto SyncNotesRespose
TomasArrachea Mar 27, 2026
12401ce
review: address small comments
TomasArrachea Mar 27, 2026
e8fea10
review: set block_to to chain tip if requested number is beyond the tip
TomasArrachea Mar 27, 2026
727099c
review: return last note update without mmr proof
TomasArrachea Mar 27, 2026
06a85a9
Merge branch 'next' into tomasarrachea-sync-notes-rework
bobbinth Mar 28, 2026
b9532b2
Merge branch 'next' into tomasarrachea-sync-notes-rework
bobbinth Mar 29, 2026
0e24e08
review: fix mmr proof comment
TomasArrachea Mar 30, 2026
889fc04
review: error if block_to is greater than chain tip
TomasArrachea Mar 30, 2026
59c4658
review: use block_end + 1 as mmr checkpoint
TomasArrachea Mar 30, 2026
0830662
chore: update proto response comment
TomasArrachea Mar 30, 2026
46495d2
chore: changelog
TomasArrachea Mar 30, 2026
31cdafd
test: mmr proof
TomasArrachea Mar 30, 2026
d6e473c
review: return the last checked block
TomasArrachea Mar 30, 2026
edb9a6c
chore: lint
TomasArrachea Mar 30, 2026
86505ae
chore: format
TomasArrachea Mar 30, 2026
168ae82
feat: update proto comment
TomasArrachea Mar 30, 2026
41d1036
feat: include chain tip in the response
TomasArrachea Mar 30, 2026
21607ad
feat: use block_num + 1 for sync pagination
TomasArrachea Mar 30, 2026
b5debf9
feat: update proto comment
TomasArrachea Mar 31, 2026
49854da
review: update select notes sql comment
TomasArrachea Mar 31, 2026
0601592
review: add comment on MMR proof
TomasArrachea Mar 31, 2026
29fb8a2
review: fix break condition on the stress test
TomasArrachea Mar 31, 2026
6a2088e
chore: typo
TomasArrachea Mar 31, 2026
cead199
Merge branch 'next' into tomasarrachea-sync-notes-rework
TomasArrachea Mar 31, 2026
ce575ae
review: update store README
TomasArrachea Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
- Add #[track_caller] to tracing/logging helpers ([#1651](https://github.com/0xMiden/node/pull/1651)).
- Added support for generic account loading at genesis ([#1624](https://github.com/0xMiden/node/pull/1624)).
- Improved tracing span fields ([#1650](https://github.com/0xMiden/node/pull/1650))
- Replaced NTX Builder's in-memory state management with SQLite-backed persistence; account states, notes, and transaction effects are now stored in the database and inflight state is purged on startup ([#1662](https://github.com/0xMiden/node/pull/1662)).
- Replaced NTX Builder's in-memory state management with SQLite-backed persistence; account states, notes, and transaction effects are now stored in the database and inflight state is purged on startup ([#1662](https://github.com/0xMiden/node/pull/1662)).
- [BREAKING] Reworked `miden-remote-prover`, removing the `worker`/`proxy` distinction and simplifying to a `worker` with a request queue ([#1688](https://github.com/0xMiden/node/pull/1688)).
- [BREAKING] Renamed `NoteRoot` protobuf message used in `GetNoteScriptByRoot` gRPC endpoints into `NoteScriptRoot` ([#1722](https://github.com/0xMiden/node/pull/1722)).
- NTX Builder actors now deactivate after being idle for a configurable idle timeout (`--ntx-builder.idle-timeout`, default 5 min) and are re-activated when new notes target their account ([#1705](https://github.com/0xMiden/node/pull/1705)).
Expand All @@ -43,6 +43,7 @@
- NTX Builder now deactivates network accounts which crash repeatedly (configurable via `--ntx-builder.max-account-crashes`, default 10) ([#1712](https://github.com/0xMiden/miden-node/pull/1712)).
- Removed gRPC reflection v1-alpha support ([#1795](https://github.com/0xMiden/node/pull/1795)).
- [BREAKING] Rust requirement bumped from `v1.91` to `v1.93` ([#1803](https://github.com/0xMiden/node/pull/1803)).
- [BREAKING] Updated `SyncNotes` endpoint to returned multiple note updates (([#1843](https://github.com/0xMiden/node/pull/1843))).
- [BREAKING] Refactored `NoteSyncRecord` to returned a fixed-size `NoteMetadataHeader` ([#1837](https://github.com/0xMiden/node/pull/1837)).

### Fixes
Expand Down
52 changes: 26 additions & 26 deletions bin/stress-test/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,25 @@ pub async fn bench_sync_nullifiers(
};
let response = store_client.sync_notes(sync_request).await.unwrap().into_inner();

let note_ids = response
.notes
let pagination = response.pagination_info.expect("pagination_info should exist");
let last_block_checked = pagination.block_num;

if response.blocks.is_empty() || last_block_checked >= pagination.chain_tip {
break;
}

// Collect note IDs from all blocks in the response.
let note_ids: Vec<_> = response
.blocks
.iter()
.map(|n| n.inclusion_proof.as_ref().unwrap().note_id.unwrap())
.collect::<Vec<proto::note::NoteId>>();
.flat_map(|b| {
b.notes.iter().map(|n| n.inclusion_proof.as_ref().unwrap().note_id.unwrap())
})
.collect();

// Get the notes nullifiers, limiting to 20 notes maximum
let note_ids_to_fetch =
note_ids.iter().take(NOTE_IDS_PER_NULLIFIERS_CHECK).copied().collect::<Vec<_>>();
// Get the notes nullifiers, limiting to 20 notes maximum.
let note_ids_to_fetch: Vec<_> =
note_ids.iter().take(NOTE_IDS_PER_NULLIFIERS_CHECK).copied().collect();
if !note_ids_to_fetch.is_empty() {
let notes = store_client
.get_notes_by_id(proto::note::NoteIdList { ids: note_ids_to_fetch })
Expand All @@ -163,25 +173,15 @@ pub async fn bench_sync_nullifiers(
.into_inner()
.notes;

nullifier_prefixes.extend(
notes
.iter()
.filter_map(|n| {
// Private notes are filtered out because `n.details` is None
let details_bytes = n.note.as_ref()?.details.as_ref()?;
let details = NoteDetails::read_from_bytes(details_bytes).unwrap();
Some(u32::from(details.nullifier().prefix()))
})
.collect::<Vec<u32>>(),
);
nullifier_prefixes.extend(notes.iter().filter_map(|n| {
let details_bytes = n.note.as_ref()?.details.as_ref()?;
let details = NoteDetails::read_from_bytes(details_bytes).unwrap();
Some(u32::from(details.nullifier().prefix()))
}));
}

// Update block number from pagination info
let pagination_info = response.pagination_info.expect("pagination_info should exist");
current_block_num = pagination_info.block_num;
if pagination_info.chain_tip == current_block_num {
break;
}
// Resume from the next block after the last one checked.
current_block_num = last_block_checked + 1;
}
let mut nullifiers = nullifier_prefixes.into_iter().cycle();

Expand Down Expand Up @@ -400,8 +400,8 @@ async fn sync_transactions_paginated(
break;
}

// Request the remaining range up to the reported chain tip
next_block_from = reached_block;
// Resume from the next block after the last one fully included.
next_block_from = reached_block + 1;
target_block_to = chain_tip;
}

Expand Down
6 changes: 2 additions & 4 deletions crates/rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,9 @@ Returns info which can be used by the client to sync up to the tip of chain for

**Limits:** `note_tag` (1000)

Client specifies the `note_tags` they are interested in, and the block range from which to search for matching notes. The request will then return the next block containing any note matching the provided tags within the specified range.
Client specifies the `note_tags` they are interested in, and the block range to search. The response contains all blocks with matching notes that fit within the response payload limit, along with each note's metadata, inclusion proof, and MMR authentication path.

The response includes each note's metadata and inclusion proof.

A basic note sync can be implemented by repeatedly requesting the previous response's block until reaching the tip of the chain.
If `response.pagination_info.block_num` is less than the requested range end, make another request starting from `response.pagination_info.block_num + 1` to continue syncing.

#### Error Handling

Expand Down
6 changes: 2 additions & 4 deletions crates/store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,9 @@ When account vault synchronization fails, detailed error information is provided

Returns info which can be used by the client to sync up to the tip of chain for the notes they are interested in.

Client specifies the `note_tags` they are interested in, and the block range from which to search for matching notes. The request will then return the next block containing any note matching the provided tags within the specified range.
Client specifies the `note_tags` they are interested in, and the block range to search. The response contains all blocks with matching notes that fit within the response payload limit, along with each note's metadata, inclusion proof, and MMR authentication path.

The response includes each note's metadata and inclusion proof.

A basic note sync can be implemented by repeatedly requesting the previous response's block until reaching the tip of the chain.
If `response.pagination_info.block_num` is less than the requested range end, make another request starting from `response.pagination_info.block_num + 1` to continue syncing.

#### Error Handling

Expand Down
7 changes: 4 additions & 3 deletions crates/store/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::mem::size_of;
use std::ops::{Deref, DerefMut, RangeInclusive};
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Context;
use diesel::{Connection, QueryableByName, RunQueryDsl, SqliteConnection};
Expand Down Expand Up @@ -487,10 +488,10 @@ impl Db {
pub async fn get_note_sync(
&self,
block_range: RangeInclusive<BlockNumber>,
note_tags: Vec<u32>,
) -> Result<(NoteSyncUpdate, BlockNumber), NoteSyncError> {
note_tags: Arc<[u32]>,
) -> Result<Option<NoteSyncUpdate>, NoteSyncError> {
self.transact("notes sync task", move |conn| {
queries::get_note_sync(conn, note_tags.as_slice(), block_range)
queries::get_note_sync(conn, &note_tags, block_range)
})
.await
}
Expand Down
21 changes: 12 additions & 9 deletions crates/store/src/db/models/queries/notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ impl From<NetworkNoteType> for i32 {
/// notes
/// WHERE
/// (tag IN (?1) OR sender IN (?2)) AND
/// block_num > ?3 AND
/// block_num >= ?3 AND
/// block_num <= ?4
/// ORDER BY
/// block_num ASC
Expand All @@ -141,7 +141,7 @@ pub(crate) fn select_notes_since_block_by_tag_and_sender(
account_ids: &[AccountId],
note_tags: &[u32],
block_range: RangeInclusive<BlockNumber>,
) -> Result<(Vec<NoteSyncRecord>, BlockNumber), DatabaseError> {
) -> Result<Vec<NoteSyncRecord>, DatabaseError> {
QueryParamAccountIdLimit::check(account_ids.len())?;
QueryParamNoteTagLimit::check(note_tags.len())?;
let desired_note_tags = Vec::from_iter(note_tags.iter().map(|tag| *tag as i32));
Expand All @@ -158,14 +158,14 @@ pub(crate) fn select_notes_since_block_by_tag_and_sender(
.eq_any(&desired_note_tags[..])
.or(schema::notes::sender.eq_any(&desired_senders[..])),
)
.filter(schema::notes::committed_at.gt(start_block_num))
.filter(schema::notes::committed_at.ge(start_block_num))
.filter(schema::notes::committed_at.le(end_block_num))
.order_by(schema::notes::committed_at.asc())
.limit(1)
.get_result(conn)
.optional()?
else {
return Ok((Vec::new(), *block_range.end()));
return Ok(Vec::new());
};

let notes = SelectDsl::select(schema::notes::table, NoteSyncRecordRawRow::as_select())
Expand All @@ -185,7 +185,7 @@ pub(crate) fn select_notes_since_block_by_tag_and_sender(
.get_results::<NoteSyncRecordRawRow>(conn)
.map_err(DatabaseError::from)?;

Ok((vec_raw_try_into(notes)?, BlockNumber::from_raw_sql(desired_block_num)?))
vec_raw_try_into(notes)
}

/// Select all notes matching the given set of identifiers
Expand Down Expand Up @@ -518,16 +518,19 @@ pub(crate) fn get_note_sync(
conn: &mut SqliteConnection,
note_tags: &[u32],
block_range: RangeInclusive<BlockNumber>,
) -> Result<(NoteSyncUpdate, BlockNumber), NoteSyncError> {
) -> Result<Option<NoteSyncUpdate>, NoteSyncError> {
QueryParamNoteTagLimit::check(note_tags.len()).map_err(DatabaseError::from)?;

let (notes, last_included_block) =
select_notes_since_block_by_tag_and_sender(conn, &[], note_tags, block_range)?;
let notes = select_notes_since_block_by_tag_and_sender(conn, &[], note_tags, block_range)?;

if notes.is_empty() {
return Ok(None);
}

let block_header =
select_block_header_by_block_num(conn, notes.first().map(|note| note.block_num))?
.ok_or(NoteSyncError::EmptyBlockHeadersTable)?;
Ok((NoteSyncUpdate { notes, block_header }, last_included_block))
Ok(Some(NoteSyncUpdate { notes, block_header }))
}

#[derive(Debug, Clone, PartialEq, Selectable, Queryable, QueryableByName)]
Expand Down
Loading
Loading