Skip to content

Conversation

@juan518munoz
Copy link
Collaborator

@juan518munoz juan518munoz commented Dec 15, 2025

Closes #1596

@juan518munoz juan518munoz added the no changelog This PR does not require an entry in the `CHANGELOG.md` file label Dec 15, 2025
@juan518munoz juan518munoz force-pushed the jmunoz-batch-note-request-to-node branch from 605e1d9 to 602a4b7 Compare December 15, 2025 17:34
Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving some comments

Comment on lines 155 to 164
for (note_id, note) in notes_by_id {
if let Some(note) = note {
if let InputNoteState::Expected(ExpectedNoteState { tag: Some(tag), .. }) =
note.state()
{
self.insert_note_tag(NoteTagRecord::with_note_source(*tag, note_id)).await?;
}
self.store.upsert_input_notes(&[note]).await?;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should be able to do this once at the end, no? Instead of for every type

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, we can declare an empty vector and extend it with the results as we retrieve them.

Addressed in d782650

/// ([`NOTE_TAG_LIMIT`](crate::rpc::NOTE_TAG_LIMIT)).
pub async fn import_notes(
&mut self,
note_files: Vec<NoteFile>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we already collect three vectors in the function itself, so maybe this could take a slice or an iterator so as to not have to make the caller collect into another list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in d782650

NoteFile::NoteWithProof(note, _) => note.id(),
};

let previous_note = self.get_input_note(id).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get many notes at once before the loop

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 82ba2c0

});

let committed_note_data = if let Some(tag) = tag {
self.check_expected_note(after_block_num, tag, note_record.details()).await?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we talked offline, we should be able to check many tags at once here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #1617 (comment)


match committed_note_data {
Some((metadata, inclusion_proof)) => {
let mut current_partial_mmr = self.store.get_current_partial_mmr().await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we only build the MMR once

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, this leads to errors that are probably related to #1205

Added comment to track it in 2e4ff05

Comment on lines 370 to 376
if let Some(block_height) = self
.rpc_api
.get_nullifier_commit_height(
&note_record.nullifier(),
inclusion_proof.location().block_num(),
)
.await?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could create a

async fn get_nullifiers_commit_height(
        &self,
        nullifier: &[Nullifier],
        block_num: BlockNumber,
    ) -> BTreeMap<Nullifier, BlockNumber>

that could sync many nullifiers at once

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in d782650

@mmagician mmagician marked this pull request as ready for review December 18, 2025 17:40
Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @SantiagoPittella for merging the latest changes! I left some comments, mostly minor (we can disregard these), but one related to returning the IDs of all the notes that were upserted instead of the ones that were passed by the user which would be great to address before merging


let sync_height = self.get_sync_height().await?;
// Import fetched notes
let mut note_requests = vec![];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we can use Vec::with_capacity(notes.len()) here

Comment on lines +241 to +242
let prefixes: Vec<u16> =
requested_nullifiers.iter().map(crate::note::Nullifier::prefix).collect();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make this function take BTreeSet<Nullifier> instead of &[Nullifier] to enforce no prefix duplicates (otherwise, we need to do this check in the call site)

/// The default implementation of this method uses
/// [`NodeRpcClient::sync_nullifiers`].
async fn get_nullifier_commit_height(
async fn get_nullifiers_commit_height(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think get_nullifier_commit_heights to emphasize the thing we are querying for (or maybe get_commit_heights_for_nullifiers?)

&self,
nullifier: &Nullifier,
requested_nullifiers: &[Nullifier],
block_num: BlockNumber,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit: I think we could name this block_from so that we make it more explicit (although the docs already mention what it's for)

Comment on lines +60 to +63
pub async fn import_notes(
&mut self,
note_files: &[NoteFile],
) -> Result<Vec<NoteId>, ClientError> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably explain that if one NoteFile is invalid (or any note is in Processing state) the whole function will error and no notes would be imported.
Though I wonder if we would prefer import the ones that can be imported and return a list of imported NoteIds (if we do this, let's do it on a separate PR/issue).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explain it in the docs,.

}

Ok(id)
Ok(note_ids)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should return the IDs of notes that got imported. import_* methods return Option because some may not be found, so really here we should return the IDs for every note in imported_notes (and update the documentation). I think this also allows us to get rid of the last note_ids.clone()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changelog This PR does not require an entry in the `CHANGELOG.md` file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable batch note imports

4 participants