diff --git a/CHANGELOG.md b/CHANGELOG.md index f653eebdd..c5ff959ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Added `SyncNotes` endpoint (#424). - Cache sql statements (#427). - Added `execution_hint` field to `Notes` table (#441). +- Implemented `GetNoteAuthenticationInfo` endpoint for both miden-store (#421). ### Fixes diff --git a/crates/block-producer/src/batch_builder/batch.rs b/crates/block-producer/src/batch_builder/batch.rs index c42ed99d0..893007b70 100644 --- a/crates/block-producer/src/batch_builder/batch.rs +++ b/crates/block-producer/src/batch_builder/batch.rs @@ -3,13 +3,11 @@ use std::{ mem, }; +use miden_node_store::state::NoteAuthenticationInfo; use miden_objects::{ accounts::{delta::AccountUpdateDetails, AccountId}, batches::BatchNoteTree, - crypto::{ - hash::blake::{Blake3Digest, Blake3_256}, - merkle::MerklePath, - }, + crypto::hash::blake::{Blake3Digest, Blake3_256}, notes::{NoteHeader, NoteId, Nullifier}, transaction::{InputNoteCommitment, OutputNote, TransactionId}, AccountDeltaError, Digest, MAX_NOTES_PER_BATCH, @@ -88,7 +86,7 @@ impl TransactionBatch { #[instrument(target = "miden-block-producer", name = "new_batch", skip_all, err)] pub fn new( txs: Vec, - found_unauthenticated_notes: BTreeMap, + found_unauthenticated_notes: NoteAuthenticationInfo, ) -> Result { let id = Self::compute_id(&txs); @@ -141,10 +139,11 @@ impl TransactionBatch { // If an unauthenticated note was found in the store, transform it to an // authenticated one (i.e. erase additional note details // except the nullifier) - found_unauthenticated_notes - .get(&input_note_header.id()) - .map(|_path| InputNoteCommitment::from(input_note.nullifier())) - .unwrap_or_else(|| input_note.clone()) + if found_unauthenticated_notes.contains_note(&input_note_header.id()) { + InputNoteCommitment::from(input_note.nullifier()) + } else { + input_note.clone() + } }, None => input_note.clone(), }; @@ -287,6 +286,9 @@ impl OutputNoteTracker { #[cfg(test)] mod tests { + use miden_objects::notes::NoteInclusionProof; + use miden_processor::crypto::MerklePath; + use super::*; use crate::test_utils::{ mock_proven_tx, @@ -401,8 +403,14 @@ mod tests { #[test] fn test_convert_unauthenticated_note_to_authenticated() { let txs = mock_proven_txs(); - let found_unauthenticated_notes = - BTreeMap::from_iter([(mock_note(5).id(), Default::default())]); + let found_unauthenticated_notes = BTreeMap::from_iter([( + mock_note(5).id(), + NoteInclusionProof::new(0, 0, MerklePath::default()).unwrap(), + )]); + let found_unauthenticated_notes = NoteAuthenticationInfo { + note_proofs: found_unauthenticated_notes, + block_proofs: Default::default(), + }; let batch = TransactionBatch::new(txs, found_unauthenticated_notes).unwrap(); let expected_input_notes = diff --git a/crates/block-producer/src/batch_builder/mod.rs b/crates/block-producer/src/batch_builder/mod.rs index d66c7a33d..513321f7e 100644 --- a/crates/block-producer/src/batch_builder/mod.rs +++ b/crates/block-producer/src/batch_builder/mod.rs @@ -179,7 +179,7 @@ where }; let missing_notes: Vec<_> = dangling_notes .into_iter() - .filter(|note_id| !stored_notes.contains_key(note_id)) + .filter(|note_id| !stored_notes.contains_note(note_id)) .collect(); if !missing_notes.is_empty() { diff --git a/crates/block-producer/src/batch_builder/tests/mod.rs b/crates/block-producer/src/batch_builder/tests/mod.rs index 77118b5c6..651897fa8 100644 --- a/crates/block-producer/src/batch_builder/tests/mod.rs +++ b/crates/block-producer/src/batch_builder/tests/mod.rs @@ -1,5 +1,6 @@ use std::iter; +use miden_objects::{crypto::merkle::Mmr, Digest}; use tokio::sync::RwLock; use super::*; @@ -260,12 +261,19 @@ async fn test_block_builder_no_missing_notes() { async fn test_block_builder_fails_if_notes_are_missing() { let accounts: Vec<_> = (1..=4).map(MockPrivateAccount::<3>::from).collect(); let notes: Vec<_> = (1..=6).map(mock_note).collect(); + // We require mmr for the note authentication to succeed. + // + // We also need two blocks worth of mmr because the mock store skips genesis. + let mut mmr = Mmr::new(); + mmr.add(Digest::new([1u32.into(), 2u32.into(), 3u32.into(), 4u32.into()])); + mmr.add(Digest::new([1u32.into(), 2u32.into(), 3u32.into(), 4u32.into()])); let store = Arc::new( MockStoreSuccessBuilder::from_accounts( accounts.iter().map(|account| (account.id, account.states[0])), ) .initial_notes([vec![OutputNote::Full(notes[0].clone())]].iter()) + .initial_chain_mmr(mmr) .build(), ); let block_builder = Arc::new(DefaultBlockBuilder::new(Arc::clone(&store), Arc::clone(&store))); diff --git a/crates/block-producer/src/block.rs b/crates/block-producer/src/block.rs index cf2b886a8..2b15fb25b 100644 --- a/crates/block-producer/src/block.rs +++ b/crates/block-producer/src/block.rs @@ -1,17 +1,15 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use miden_node_proto::{ errors::{ConversionError, MissingFieldHelper}, generated::responses::GetBlockInputsResponse, AccountInputRecord, NullifierWitness, }; +use miden_node_store::state::NoteAuthenticationInfo; use miden_objects::{ accounts::AccountId, - crypto::{ - hash::rpo::RpoDigest, - merkle::{MerklePath, MmrPeaks, SmtProof}, - }, - notes::{NoteId, Nullifier}, + crypto::merkle::{MerklePath, MmrPeaks, SmtProof}, + notes::Nullifier, BlockHeader, Digest, }; @@ -36,7 +34,7 @@ pub struct BlockInputs { pub nullifiers: BTreeMap, /// List of unauthenticated notes found in the store - pub found_unauthenticated_notes: BTreeSet, + pub found_unauthenticated_notes: NoteAuthenticationInfo, } #[derive(Clone, Debug, Default)] @@ -95,9 +93,8 @@ impl TryFrom for BlockInputs { let found_unauthenticated_notes = response .found_unauthenticated_notes - .into_iter() - .map(|digest| Ok(RpoDigest::try_from(digest)?.into())) - .collect::>()?; + .ok_or(GetBlockInputsResponse::missing_field("found_authenticated_notes"))? + .try_into()?; Ok(Self { block_header, diff --git a/crates/block-producer/src/block_builder/mod.rs b/crates/block-producer/src/block_builder/mod.rs index 7783d95ea..0522d1140 100644 --- a/crates/block-producer/src/block_builder/mod.rs +++ b/crates/block-producer/src/block_builder/mod.rs @@ -114,7 +114,7 @@ where .await?; let missing_notes: Vec<_> = dangling_notes - .difference(&block_inputs.found_unauthenticated_notes) + .difference(&block_inputs.found_unauthenticated_notes.note_ids()) .copied() .collect(); if !missing_notes.is_empty() { diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index 8680298ff..6e72b5611 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -9,9 +9,9 @@ use itertools::Itertools; use miden_node_proto::{ errors::{ConversionError, MissingFieldHelper}, generated::{ - digest, note, + digest, requests::{ - ApplyBlockRequest, GetBlockInputsRequest, GetNotesByIdRequest, + ApplyBlockRequest, GetBlockInputsRequest, GetNoteAuthenticationInfoRequest, GetTransactionInputsRequest, }, responses::{GetTransactionInputsResponse, NullifierTransactionInputRecord}, @@ -19,11 +19,11 @@ use miden_node_proto::{ }, AccountState, }; +use miden_node_store::state::NoteAuthenticationInfo; use miden_node_utils::formatting::format_opt; use miden_objects::{ accounts::AccountId, block::Block, - crypto::merkle::MerklePath, notes::{NoteId, Nullifier}, utils::Serializable, Digest, @@ -56,15 +56,12 @@ pub trait Store: ApplyBlock { /// Returns note authentication information for the set of specified notes. /// - /// If authentication info could for a note does not exist in the store, the note is omitted + /// If authentication info for a note does not exist in the store, the note is omitted /// from the returned set of notes. - /// - /// TODO: right now this return only Merkle paths per note, but this will need to be updated to - /// return full authentication info. async fn get_note_authentication_info( &self, notes: impl Iterator + Send, - ) -> Result, NotePathsError>; + ) -> Result; } #[async_trait] @@ -257,33 +254,24 @@ impl Store for DefaultStore { async fn get_note_authentication_info( &self, notes: impl Iterator + Send, - ) -> Result, NotePathsError> { - let request = tonic::Request::new(GetNotesByIdRequest { + ) -> Result { + let request = tonic::Request::new(GetNoteAuthenticationInfoRequest { note_ids: notes.map(digest::Digest::from).collect(), }); let store_response = self .store .clone() - .get_notes_by_id(request) + .get_note_authentication_info(request) .await .map_err(|err| NotePathsError::GrpcClientError(err.message().to_string()))? .into_inner(); - Ok(store_response - .notes - .into_iter() - .map(|note| { - Ok(( - RpoDigest::try_from( - note.note_id.ok_or(note::Note::missing_field(stringify!(note_id)))?, - )? - .into(), - note.merkle_path - .ok_or(note::Note::missing_field(stringify!(merkle_path)))? - .try_into()?, - )) - }) - .collect::, ConversionError>>()?) + let note_authentication_info = store_response + .proofs + .ok_or(GetTransactionInputsResponse::missing_field("proofs"))? + .try_into()?; + + Ok(note_authentication_info) } } diff --git a/crates/block-producer/src/test_utils/block.rs b/crates/block-producer/src/test_utils/block.rs index e5c841abc..fad8de3af 100644 --- a/crates/block-producer/src/test_utils/block.rs +++ b/crates/block-producer/src/test_utils/block.rs @@ -22,7 +22,14 @@ pub async fn build_expected_block_header( store: &MockStoreSuccess, batches: &[TransactionBatch], ) -> BlockHeader { - let last_block_header = *store.last_block_header.read().await; + let last_block_header = *store + .block_headers + .read() + .await + .iter() + .max_by_key(|(block_num, _)| *block_num) + .unwrap() + .1; // Compute new account root let updated_accounts: Vec<_> = @@ -104,7 +111,14 @@ impl MockBlockBuilder { Self { store_accounts: store.accounts.read().await.clone(), store_chain_mmr: store.chain_mmr.read().await.clone(), - last_block_header: *store.last_block_header.read().await, + last_block_header: *store + .block_headers + .read() + .await + .iter() + .max_by_key(|(block_num, _)| *block_num) + .unwrap() + .1, updated_accounts: None, created_notes: None, diff --git a/crates/block-producer/src/test_utils/store.rs b/crates/block-producer/src/test_utils/store.rs index 0f3b647ac..50e3eed85 100644 --- a/crates/block-producer/src/test_utils/store.rs +++ b/crates/block-producer/src/test_utils/store.rs @@ -5,10 +5,12 @@ use std::{ }; use async_trait::async_trait; +use miden_node_proto::domain::blocks::BlockInclusionProof; +use miden_node_store::state::NoteAuthenticationInfo; use miden_objects::{ - block::{Block, BlockNoteTree, NoteBatch}, - crypto::merkle::{MerklePath, Mmr, SimpleSmt, Smt, ValuePath}, - notes::{NoteId, Nullifier}, + block::{Block, NoteBatch}, + crypto::merkle::{Mmr, SimpleSmt, Smt, ValuePath}, + notes::{NoteId, NoteInclusionProof, Nullifier}, BlockHeader, ACCOUNT_TREE_DEPTH, EMPTY_WORD, ZERO, }; @@ -30,8 +32,7 @@ use crate::{ #[derive(Debug)] pub struct MockStoreSuccessBuilder { accounts: Option>, - notes: Option>, - note_root: Option, + notes: Option>, produced_nullifiers: Option>, chain_mmr: Option, block_num: Option, @@ -49,12 +50,9 @@ impl MockStoreSuccessBuilder { SimpleSmt::::with_leaves(accounts).unwrap() }; - let (note_tree, notes) = Self::populate_note_trees(block_output_notes(batches_iter)); - Self { accounts: Some(accounts_smt), - notes: Some(notes), - note_root: Some(note_tree.root()), + notes: Some(block_output_notes(batches_iter).cloned().collect()), produced_nullifiers: None, chain_mmr: None, block_num: None, @@ -71,7 +69,6 @@ impl MockStoreSuccessBuilder { Self { accounts: Some(accounts_smt), notes: None, - note_root: None, produced_nullifiers: None, chain_mmr: None, block_num: None, @@ -79,10 +76,7 @@ impl MockStoreSuccessBuilder { } pub fn initial_notes<'a>(mut self, notes: impl Iterator + Clone) -> Self { - let (note_tree, notes) = Self::populate_note_trees(notes); - - self.notes = Some(notes); - self.note_root = Some(note_tree.root()); + self.notes = Some(notes.cloned().collect()); self } @@ -105,22 +99,12 @@ impl MockStoreSuccessBuilder { self } - fn populate_note_trees<'a>( - batches_iterator: impl Iterator + Clone, - ) -> (BlockNoteTree, BTreeMap) { - let block_note_tree = note_created_smt_from_note_batches(batches_iterator.clone()); - let note_map = flatten_output_notes(batches_iterator) - .map(|(index, note)| (note.id(), block_note_tree.get_note_path(index).unwrap())) - .collect(); - - (block_note_tree, note_map) - } - pub fn build(self) -> MockStoreSuccess { let block_num = self.block_num.unwrap_or(1); let accounts_smt = self.accounts.unwrap_or(SimpleSmt::new().unwrap()); let notes = self.notes.unwrap_or_default(); - let note_root = self.note_root.unwrap_or_default(); + let block_note_tree = note_created_smt_from_note_batches(notes.iter()); + let note_root = block_note_tree.root(); let chain_mmr = self.chain_mmr.unwrap_or_default(); let nullifiers_smt = self .produced_nullifiers @@ -147,11 +131,28 @@ impl MockStoreSuccessBuilder { 1, ); + let notes = flatten_output_notes(notes.iter()) + .map(|(index, note)| { + ( + note.id(), + NoteInclusionProof::new( + block_num, + index.to_absolute_index(), + block_note_tree.get_note_path(index).unwrap(), + ) + .expect("Failed to create `NoteInclusionProof`"), + ) + }) + .collect(); + MockStoreSuccess { accounts: Arc::new(RwLock::new(accounts_smt)), produced_nullifiers: Arc::new(RwLock::new(nullifiers_smt)), chain_mmr: Arc::new(RwLock::new(chain_mmr)), - last_block_header: Arc::new(RwLock::new(initial_block_header)), + block_headers: Arc::new(RwLock::new(BTreeMap::from_iter([( + initial_block_header.block_num(), + initial_block_header, + )]))), num_apply_block_called: Default::default(), notes: Arc::new(RwLock::new(notes)), } @@ -168,14 +169,14 @@ pub struct MockStoreSuccess { /// Stores the chain MMR pub chain_mmr: Arc>, - /// Stores the header of the last applied block - pub last_block_header: Arc>, + /// The chains block headers. + pub block_headers: Arc>>, /// The number of times `apply_block()` was called pub num_apply_block_called: Arc>, /// Maps note id -> note inclusion proof for all created notes - pub notes: Arc>>, + pub notes: Arc>>, } impl MockStoreSuccess { @@ -198,12 +199,13 @@ impl ApplyBlock for MockStoreSuccess { for update in block.updated_accounts() { locked_accounts.insert(update.account_id().into(), update.new_state_hash().into()); } - debug_assert_eq!(locked_accounts.root(), block.header().account_root()); + let header = block.header(); + debug_assert_eq!(locked_accounts.root(), header.account_root()); // update nullifiers for nullifier in block.nullifiers() { locked_produced_nullifiers - .insert(nullifier.inner(), [block.header().block_num().into(), ZERO, ZERO, ZERO]); + .insert(nullifier.inner(), [header.block_num().into(), ZERO, ZERO, ZERO]); } // update chain mmr with new block header hash @@ -219,11 +221,19 @@ impl ApplyBlock for MockStoreSuccess { // update notes let mut locked_notes = self.notes.write().await; for (note_index, note) in block.notes() { - locked_notes.insert(note.id(), note_tree.get_note_path(note_index).unwrap_or_default()); + locked_notes.insert( + note.id(), + NoteInclusionProof::new( + header.block_num(), + note_index.to_absolute_index(), + note_tree.get_note_path(note_index).unwrap_or_default(), + ) + .expect("Failed to build `NoteInclusionProof`"), + ); } - // update last block header - *self.last_block_header.write().await = block.header(); + // append the block header + self.block_headers.write().await.insert(header.block_num(), header); // update num_apply_block_called *self.num_apply_block_called.write().await += 1; @@ -309,11 +319,33 @@ impl Store for MockStoreSuccess { .collect(); let locked_notes = self.notes.read().await; - let found_unauthenticated_notes = - notes.filter(|&id| locked_notes.contains_key(id)).copied().collect(); + let note_proofs = notes + .filter_map(|id| locked_notes.get(id).map(|proof| (*id, proof.clone()))) + .collect::>(); + + let locked_headers = self.block_headers.read().await; + let latest_header = + *locked_headers.iter().max_by_key(|(block_num, _)| *block_num).unwrap().1; + + let locked_chain_mmr = self.chain_mmr.read().await; + let mmr_forest = locked_chain_mmr.forest(); + let chain_length = latest_header.block_num(); + let block_proofs = note_proofs + .values() + .map(|note_proof| { + let block_num = note_proof.location().block_num(); + let block_header = *locked_headers.get(&block_num).unwrap(); + let mmr_path = + locked_chain_mmr.open(block_num as usize, mmr_forest).unwrap().merkle_path; + + BlockInclusionProof { block_header, mmr_path, chain_length } + }) + .collect(); + + let found_unauthenticated_notes = NoteAuthenticationInfo { block_proofs, note_proofs }; Ok(BlockInputs { - block_header: *self.last_block_header.read().await, + block_header: latest_header, chain_peaks, accounts, nullifiers, @@ -324,13 +356,34 @@ impl Store for MockStoreSuccess { async fn get_note_authentication_info( &self, notes: impl Iterator + Send, - ) -> Result, NotePathsError> { + ) -> Result { let locked_notes = self.notes.read().await; - let note_auth_info = notes - .filter_map(|note_id| locked_notes.get(note_id).map(|path| (*note_id, path.clone()))) + let locked_headers = self.block_headers.read().await; + let locked_chain_mmr = self.chain_mmr.read().await; + + let note_proofs = notes + .filter_map(|id| locked_notes.get(id).map(|proof| (*id, proof.clone()))) + .collect::>(); + + let latest_header = + *locked_headers.iter().max_by_key(|(block_num, _)| *block_num).unwrap().1; + let chain_length = latest_header.block_num(); + + let block_proofs = note_proofs + .values() + .map(|note_proof| { + let block_num = note_proof.location().block_num(); + let block_header = *locked_headers.get(&block_num).unwrap(); + let mmr_path = locked_chain_mmr + .open(block_num as usize, latest_header.block_num() as usize) + .unwrap() + .merkle_path; + + BlockInclusionProof { block_header, mmr_path, chain_length } + }) .collect(); - Ok(note_auth_info) + Ok(NoteAuthenticationInfo { block_proofs, note_proofs }) } } @@ -365,7 +418,7 @@ impl Store for MockStoreFailure { async fn get_note_authentication_info( &self, _notes: impl Iterator + Send, - ) -> Result, NotePathsError> { + ) -> Result { Err(NotePathsError::GrpcClientError(String::new())) } } diff --git a/crates/proto/src/domain/accounts.rs b/crates/proto/src/domain/accounts.rs index ee669bef2..cc9fb7306 100644 --- a/crates/proto/src/domain/accounts.rs +++ b/crates/proto/src/domain/accounts.rs @@ -122,7 +122,7 @@ impl From for AccountBlockInputRecord { Self { account_id: Some(from.account_id.into()), account_hash: Some(from.account_hash.into()), - proof: Some(from.proof.into()), + proof: Some(Into::into(&from.proof)), } } } @@ -142,6 +142,7 @@ impl TryFrom for AccountInputRecord { .try_into()?, proof: account_input_record .proof + .as_ref() .ok_or(AccountBlockInputRecord::missing_field(stringify!(proof)))? .try_into()?, }) diff --git a/crates/proto/src/domain/blocks.rs b/crates/proto/src/domain/blocks.rs index bc6223427..a1c496b7d 100644 --- a/crates/proto/src/domain/blocks.rs +++ b/crates/proto/src/domain/blocks.rs @@ -1,14 +1,14 @@ -use miden_objects::BlockHeader; +use miden_objects::{crypto::merkle::MerklePath, BlockHeader}; use crate::{ errors::{ConversionError, MissingFieldHelper}, - generated::block_header, + generated::block, }; // BLOCK HEADER // ================================================================================================ -impl From<&BlockHeader> for block_header::BlockHeader { +impl From<&BlockHeader> for block::BlockHeader { fn from(header: &BlockHeader) -> Self { Self { version: header.version(), @@ -25,56 +25,94 @@ impl From<&BlockHeader> for block_header::BlockHeader { } } -impl From for block_header::BlockHeader { +impl From for block::BlockHeader { fn from(header: BlockHeader) -> Self { (&header).into() } } -impl TryFrom<&block_header::BlockHeader> for BlockHeader { +impl TryFrom<&block::BlockHeader> for BlockHeader { type Error = ConversionError; - fn try_from(value: &block_header::BlockHeader) -> Result { + fn try_from(value: &block::BlockHeader) -> Result { value.clone().try_into() } } -impl TryFrom for BlockHeader { +impl TryFrom for BlockHeader { type Error = ConversionError; - fn try_from(value: block_header::BlockHeader) -> Result { + fn try_from(value: block::BlockHeader) -> Result { Ok(BlockHeader::new( value.version, value .prev_hash - .ok_or(block_header::BlockHeader::missing_field(stringify!(prev_hash)))? + .ok_or(block::BlockHeader::missing_field(stringify!(prev_hash)))? .try_into()?, value.block_num, value .chain_root - .ok_or(block_header::BlockHeader::missing_field(stringify!(chain_root)))? + .ok_or(block::BlockHeader::missing_field(stringify!(chain_root)))? .try_into()?, value .account_root - .ok_or(block_header::BlockHeader::missing_field(stringify!(account_root)))? + .ok_or(block::BlockHeader::missing_field(stringify!(account_root)))? .try_into()?, value .nullifier_root - .ok_or(block_header::BlockHeader::missing_field(stringify!(nullifier_root)))? + .ok_or(block::BlockHeader::missing_field(stringify!(nullifier_root)))? .try_into()?, value .note_root - .ok_or(block_header::BlockHeader::missing_field(stringify!(note_root)))? + .ok_or(block::BlockHeader::missing_field(stringify!(note_root)))? .try_into()?, value .tx_hash - .ok_or(block_header::BlockHeader::missing_field(stringify!(tx_hash)))? + .ok_or(block::BlockHeader::missing_field(stringify!(tx_hash)))? .try_into()?, value .proof_hash - .ok_or(block_header::BlockHeader::missing_field(stringify!(proof_hash)))? + .ok_or(block::BlockHeader::missing_field(stringify!(proof_hash)))? .try_into()?, value.timestamp, )) } } + +/// Data required to verify a block's inclusion proof. +#[derive(Clone, Debug)] +pub struct BlockInclusionProof { + pub block_header: BlockHeader, + pub mmr_path: MerklePath, + pub chain_length: u32, +} + +impl From for block::BlockInclusionProof { + fn from(value: BlockInclusionProof) -> Self { + Self { + block_header: Some(value.block_header.into()), + mmr_path: Some((&value.mmr_path).into()), + chain_length: value.chain_length, + } + } +} + +impl TryFrom for BlockInclusionProof { + type Error = ConversionError; + + fn try_from(value: block::BlockInclusionProof) -> Result { + let result = Self { + block_header: value + .block_header + .ok_or(block::BlockInclusionProof::missing_field("block_header"))? + .try_into()?, + mmr_path: (&value + .mmr_path + .ok_or(block::BlockInclusionProof::missing_field("mmr_path"))?) + .try_into()?, + chain_length: value.chain_length, + }; + + Ok(result) + } +} diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index 161467d09..6b71d18f3 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -12,18 +12,18 @@ use crate::{ // MERKLE PATH // ================================================================================================ -impl From for generated::merkle::MerklePath { - fn from(value: MerklePath) -> Self { +impl From<&MerklePath> for generated::merkle::MerklePath { + fn from(value: &MerklePath) -> Self { let siblings = value.nodes().iter().map(generated::digest::Digest::from).collect(); generated::merkle::MerklePath { siblings } } } -impl TryFrom for MerklePath { +impl TryFrom<&generated::merkle::MerklePath> for MerklePath { type Error = ConversionError; - fn try_from(merkle_path: generated::merkle::MerklePath) -> Result { - merkle_path.siblings.into_iter().map(Digest::try_from).collect() + fn try_from(merkle_path: &generated::merkle::MerklePath) -> Result { + merkle_path.siblings.iter().map(Digest::try_from).collect() } } @@ -135,6 +135,7 @@ impl TryFrom for SmtProof { fn try_from(opening: generated::smt::SmtOpening) -> Result { let path: MerklePath = opening .path + .as_ref() .ok_or(generated::smt::SmtOpening::missing_field(stringify!(path)))? .try_into()?; let leaf: SmtLeaf = opening @@ -148,7 +149,7 @@ impl TryFrom for SmtProof { impl From for generated::smt::SmtOpening { fn from(proof: SmtProof) -> Self { - let (path, leaf) = proof.into_parts(); + let (ref path, leaf) = proof.into_parts(); Self { path: Some(path.into()), leaf: Some(leaf.into()), diff --git a/crates/proto/src/domain/mod.rs b/crates/proto/src/domain/mod.rs index aa1733512..43da3d479 100644 --- a/crates/proto/src/domain/mod.rs +++ b/crates/proto/src/domain/mod.rs @@ -9,18 +9,20 @@ pub mod transactions; // UTILITIES // ================================================================================================ -pub fn convert(from: T) -> Vec +pub fn convert(from: T) -> R where T: IntoIterator, From: Into, + R: FromIterator, { from.into_iter().map(|e| e.into()).collect() } -pub fn try_convert(from: T) -> Result, E> +pub fn try_convert(from: T) -> Result where T: IntoIterator, From: TryInto, + R: FromIterator, { from.into_iter().map(|e| e.try_into()).collect() } diff --git a/crates/proto/src/domain/notes.rs b/crates/proto/src/domain/notes.rs index 45f0449af..6e92214ca 100644 --- a/crates/proto/src/domain/notes.rs +++ b/crates/proto/src/domain/notes.rs @@ -1,17 +1,22 @@ use miden_objects::{ - notes::{NoteExecutionHint, NoteMetadata, NoteTag, NoteType}, - Felt, + notes::{NoteExecutionHint, NoteId, NoteInclusionProof, NoteMetadata, NoteTag, NoteType}, + Digest, Felt, }; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::{ + errors::{ConversionError, MissingFieldHelper}, + generated::note::{ + NoteInclusionInBlockProof as NoteInclusionInBlockProofPb, NoteMetadata as NoteMetadataPb, + }, +}; -impl TryFrom for NoteMetadata { +impl TryFrom for NoteMetadata { type Error = ConversionError; - fn try_from(value: crate::generated::note::NoteMetadata) -> Result { + fn try_from(value: NoteMetadataPb) -> Result { let sender = value .sender - .ok_or_else(|| crate::generated::note::NoteMetadata::missing_field("Sender"))? + .ok_or_else(|| NoteMetadataPb::missing_field(stringify!(sender)))? .try_into()?; let note_type = NoteType::try_from(value.note_type as u64)?; let tag = NoteTag::from(value.tag); @@ -24,7 +29,7 @@ impl TryFrom for NoteMetadata { } } -impl From for crate::generated::note::NoteMetadata { +impl From for NoteMetadataPb { fn from(val: NoteMetadata) -> Self { let sender = Some(val.sender().into()); let note_type = val.note_type() as u32; @@ -41,3 +46,41 @@ impl From for crate::generated::note::NoteMetadata { } } } + +impl From<(&NoteId, &NoteInclusionProof)> for NoteInclusionInBlockProofPb { + fn from((note_id, proof): (&NoteId, &NoteInclusionProof)) -> Self { + Self { + note_id: Some(note_id.into()), + block_num: proof.location().block_num(), + note_index_in_block: proof.location().node_index_in_block(), + merkle_path: Some(Into::into(proof.note_path())), + } + } +} + +impl TryFrom<&NoteInclusionInBlockProofPb> for (NoteId, NoteInclusionProof) { + type Error = ConversionError; + + fn try_from( + proof: &NoteInclusionInBlockProofPb, + ) -> Result<(NoteId, NoteInclusionProof), Self::Error> { + Ok(( + Digest::try_from( + proof + .note_id + .as_ref() + .ok_or(NoteInclusionInBlockProofPb::missing_field(stringify!(note_id)))?, + )? + .into(), + NoteInclusionProof::new( + proof.block_num, + proof.note_index_in_block, + proof + .merkle_path + .as_ref() + .ok_or(NoteInclusionInBlockProofPb::missing_field(stringify!(merkle_path)))? + .try_into()?, + )?, + )) + } +} diff --git a/crates/proto/src/errors.rs b/crates/proto/src/errors.rs index f5fb1beff..a97490f58 100644 --- a/crates/proto/src/errors.rs +++ b/crates/proto/src/errors.rs @@ -7,6 +7,8 @@ use thiserror::Error; pub enum ConversionError { #[error("Hex error: {0}")] HexError(#[from] hex::FromHexError), + #[error("Note error: {0}")] + NoteError(#[from] miden_objects::NoteError), #[error("SMT leaf error: {0}")] SmtLeafError(#[from] SmtLeafError), #[error("SMT proof error: {0}")] @@ -17,8 +19,6 @@ pub enum ConversionError { InsufficientData { expected: usize, got: usize }, #[error("Value is not in the range 0..MODULUS")] NotAValidFelt, - #[error("Invalid note type value: {0}")] - NoteTypeError(#[from] miden_objects::NoteError), #[error("Field `{field_name}` required to be filled in protobuf representation of {entity}")] MissingFieldInProtobufRepresentation { entity: &'static str, diff --git a/crates/proto/src/generated/block_header.rs b/crates/proto/src/generated/block.rs similarity index 78% rename from crates/proto/src/generated/block_header.rs rename to crates/proto/src/generated/block.rs index a0fd23e51..14ca28944 100644 --- a/crates/proto/src/generated/block_header.rs +++ b/crates/proto/src/generated/block.rs @@ -33,3 +33,14 @@ pub struct BlockHeader { #[prost(fixed32, tag = "10")] pub timestamp: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockInclusionProof { + #[prost(message, optional, tag = "1")] + pub block_header: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub mmr_path: ::core::option::Option, + /// The chain length associated with `mmr_path`. + #[prost(fixed32, tag = "3")] + pub chain_length: u32, +} diff --git a/crates/proto/src/generated/mod.rs b/crates/proto/src/generated/mod.rs index aa4409800..13d511099 100644 --- a/crates/proto/src/generated/mod.rs +++ b/crates/proto/src/generated/mod.rs @@ -1,7 +1,7 @@ // Generated by build.rs pub mod account; -pub mod block_header; +pub mod block; pub mod block_producer; pub mod digest; pub mod merkle; diff --git a/crates/proto/src/generated/note.rs b/crates/proto/src/generated/note.rs index ee26da1cb..74f462e54 100644 --- a/crates/proto/src/generated/note.rs +++ b/crates/proto/src/generated/note.rs @@ -33,6 +33,18 @@ pub struct Note { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct NoteInclusionInBlockProof { + #[prost(message, optional, tag = "1")] + pub note_id: ::core::option::Option, + #[prost(fixed32, tag = "2")] + pub block_num: u32, + #[prost(uint32, tag = "3")] + pub note_index_in_block: u32, + #[prost(message, optional, tag = "4")] + pub merkle_path: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NoteSyncRecord { #[prost(uint32, tag = "1")] pub note_index: u32, @@ -43,3 +55,13 @@ pub struct NoteSyncRecord { #[prost(message, optional, tag = "4")] pub merkle_path: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NoteAuthenticationInfo { + /// Proof of each note's inclusion in a block. + #[prost(message, repeated, tag = "1")] + pub note_proofs: ::prost::alloc::vec::Vec, + /// Proof of each block's inclusion in the chain. + #[prost(message, repeated, tag = "2")] + pub block_proofs: ::prost::alloc::vec::Vec, +} diff --git a/crates/proto/src/generated/requests.rs b/crates/proto/src/generated/requests.rs index a75da6f50..4b8124487 100644 --- a/crates/proto/src/generated/requests.rs +++ b/crates/proto/src/generated/requests.rs @@ -121,6 +121,13 @@ pub struct GetNotesByIdRequest { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetNoteAuthenticationInfoRequest { + /// List of NoteId's to be queried from the database + #[prost(message, repeated, tag = "1")] + pub note_ids: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ListNullifiersRequest {} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/proto/src/generated/responses.rs b/crates/proto/src/generated/responses.rs index ee0f28dfa..a09bd4caf 100644 --- a/crates/proto/src/generated/responses.rs +++ b/crates/proto/src/generated/responses.rs @@ -21,7 +21,7 @@ pub struct CheckNullifiersByPrefixResponse { pub struct GetBlockHeaderByNumberResponse { /// The requested block header #[prost(message, optional, tag = "1")] - pub block_header: ::core::option::Option, + pub block_header: ::core::option::Option, /// Merkle path to verify the block's inclusion in the MMR at the returned `chain_length` #[prost(message, optional, tag = "2")] pub mmr_path: ::core::option::Option, @@ -45,7 +45,7 @@ pub struct SyncStateResponse { pub chain_tip: u32, /// Block header of the block with the first note matching the specified criteria #[prost(message, optional, tag = "2")] - pub block_header: ::core::option::Option, + pub block_header: ::core::option::Option, /// Data needed to update the partial MMR from `request.block_num + 1` to `response.block_header.block_num` #[prost(message, optional, tag = "3")] pub mmr_delta: ::core::option::Option, @@ -71,7 +71,7 @@ pub struct SyncNoteResponse { pub chain_tip: u32, /// Block header of the block with the first note matching the specified criteria #[prost(message, optional, tag = "2")] - pub block_header: ::core::option::Option, + pub block_header: ::core::option::Option, /// Merkle path to verify the block's inclusion in the MMR at the returned `chain_tip`. /// /// An MMR proof can be constructed for the leaf of index `block_header.block_num` of @@ -107,7 +107,7 @@ pub struct NullifierBlockInputRecord { pub struct GetBlockInputsResponse { /// The latest block header #[prost(message, optional, tag = "1")] - pub block_header: ::core::option::Option, + pub block_header: ::core::option::Option, /// Peaks of the above block's mmr, The `forest` value is equal to the block number #[prost(message, repeated, tag = "2")] pub mmr_peaks: ::prost::alloc::vec::Vec, @@ -118,8 +118,10 @@ pub struct GetBlockInputsResponse { #[prost(message, repeated, tag = "4")] pub nullifiers: ::prost::alloc::vec::Vec, /// The list of requested notes which were found in the database - #[prost(message, repeated, tag = "5")] - pub found_unauthenticated_notes: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "5")] + pub found_unauthenticated_notes: ::core::option::Option< + super::note::NoteAuthenticationInfo, + >, } /// An account returned as a response to the GetTransactionInputs #[allow(clippy::derive_partial_eq_without_eq)] @@ -163,6 +165,12 @@ pub struct GetNotesByIdResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetNoteAuthenticationInfoResponse { + #[prost(message, optional, tag = "1")] + pub proofs: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ListNullifiersResponse { /// Lists all nullifiers of the current chain #[prost(message, repeated, tag = "1")] diff --git a/crates/proto/src/generated/store.rs b/crates/proto/src/generated/store.rs index 35b9eb598..f14135837 100644 --- a/crates/proto/src/generated/store.rs +++ b/crates/proto/src/generated/store.rs @@ -291,6 +291,33 @@ pub mod api_client { req.extensions_mut().insert(GrpcMethod::new("store.Api", "GetBlockInputs")); self.inner.unary(req, path, codec).await } + pub async fn get_note_authentication_info( + &mut self, + request: impl tonic::IntoRequest< + super::super::requests::GetNoteAuthenticationInfoRequest, + >, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/store.Api/GetNoteAuthenticationInfo", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("store.Api", "GetNoteAuthenticationInfo")); + self.inner.unary(req, path, codec).await + } pub async fn get_notes_by_id( &mut self, request: impl tonic::IntoRequest, @@ -521,6 +548,15 @@ pub mod api_server { tonic::Response, tonic::Status, >; + async fn get_note_authentication_info( + &self, + request: tonic::Request< + super::super::requests::GetNoteAuthenticationInfoRequest, + >, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn get_notes_by_id( &self, request: tonic::Request, @@ -1044,6 +1080,56 @@ pub mod api_server { }; Box::pin(fut) } + "/store.Api/GetNoteAuthenticationInfo" => { + #[allow(non_camel_case_types)] + struct GetNoteAuthenticationInfoSvc(pub Arc); + impl< + T: Api, + > tonic::server::UnaryService< + super::super::requests::GetNoteAuthenticationInfoRequest, + > for GetNoteAuthenticationInfoSvc { + type Response = super::super::responses::GetNoteAuthenticationInfoResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::super::requests::GetNoteAuthenticationInfoRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_note_authentication_info(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetNoteAuthenticationInfoSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/store.Api/GetNotesById" => { #[allow(non_camel_case_types)] struct GetNotesByIdSvc(pub Arc); diff --git a/crates/rpc-proto/proto/block_header.proto b/crates/rpc-proto/proto/block.proto similarity index 81% rename from crates/rpc-proto/proto/block_header.proto rename to crates/rpc-proto/proto/block.proto index fad1b6c40..85117e751 100644 --- a/crates/rpc-proto/proto/block_header.proto +++ b/crates/rpc-proto/proto/block.proto @@ -1,7 +1,8 @@ syntax = "proto3"; -package block_header; +package block; import "digest.proto"; +import "merkle.proto"; message BlockHeader { // specifies the version of the protocol. @@ -25,3 +26,10 @@ message BlockHeader { // the time when the block was created. fixed32 timestamp = 10; } + +message BlockInclusionProof { + BlockHeader block_header = 1; + merkle.MerklePath mmr_path = 2; + // The chain length associated with `mmr_path`. + fixed32 chain_length = 3; +} diff --git a/crates/rpc-proto/proto/note.proto b/crates/rpc-proto/proto/note.proto index f67628690..24d7579a2 100644 --- a/crates/rpc-proto/proto/note.proto +++ b/crates/rpc-proto/proto/note.proto @@ -1,9 +1,10 @@ syntax = "proto3"; package note; +import "account.proto"; +import "block.proto"; import "digest.proto"; import "merkle.proto"; -import "account.proto"; message NoteMetadata { account.AccountId sender = 1; @@ -24,9 +25,23 @@ message Note { optional bytes details = 6; } +message NoteInclusionInBlockProof { + digest.Digest note_id = 1; + fixed32 block_num = 2; + uint32 note_index_in_block = 3; + merkle.MerklePath merkle_path = 4; +} + message NoteSyncRecord { uint32 note_index = 1; digest.Digest note_id = 2; NoteMetadata metadata = 3; merkle.MerklePath merkle_path = 4; } + +message NoteAuthenticationInfo { + // Proof of each note's inclusion in a block. + repeated note.NoteInclusionInBlockProof note_proofs = 1; + // Proof of each block's inclusion in the chain. + repeated block.BlockInclusionProof block_proofs = 2; +} diff --git a/crates/rpc-proto/proto/requests.proto b/crates/rpc-proto/proto/requests.proto index 02222f994..07dbe0752 100644 --- a/crates/rpc-proto/proto/requests.proto +++ b/crates/rpc-proto/proto/requests.proto @@ -2,7 +2,6 @@ syntax = "proto3"; package requests; import "account.proto"; -import "block_header.proto"; import "digest.proto"; import "note.proto"; @@ -100,6 +99,11 @@ message GetNotesByIdRequest { repeated digest.Digest note_ids = 1; } +message GetNoteAuthenticationInfoRequest { + // List of NoteId's to be queried from the database + repeated digest.Digest note_ids = 1; +} + message ListNullifiersRequest {} message ListAccountsRequest {} diff --git a/crates/rpc-proto/proto/responses.proto b/crates/rpc-proto/proto/responses.proto index ae9b8d99e..65b8a4e5c 100644 --- a/crates/rpc-proto/proto/responses.proto +++ b/crates/rpc-proto/proto/responses.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package responses; import "account.proto"; -import "block_header.proto"; +import "block.proto"; import "digest.proto"; import "merkle.proto"; import "mmr.proto"; @@ -24,7 +24,7 @@ message CheckNullifiersByPrefixResponse { message GetBlockHeaderByNumberResponse { // The requested block header - block_header.BlockHeader block_header = 1; + block.BlockHeader block_header = 1; // Merkle path to verify the block's inclusion in the MMR at the returned `chain_length` optional merkle.MerklePath mmr_path = 2; @@ -43,7 +43,7 @@ message SyncStateResponse { fixed32 chain_tip = 1; // Block header of the block with the first note matching the specified criteria - block_header.BlockHeader block_header = 2; + block.BlockHeader block_header = 2; // Data needed to update the partial MMR from `request.block_num + 1` to `response.block_header.block_num` mmr.MmrDelta mmr_delta = 3; @@ -67,7 +67,7 @@ message SyncNoteResponse { fixed32 chain_tip = 1; // Block header of the block with the first note matching the specified criteria - block_header.BlockHeader block_header = 2; + block.BlockHeader block_header = 2; // Merkle path to verify the block's inclusion in the MMR at the returned `chain_tip`. // @@ -94,7 +94,7 @@ message NullifierBlockInputRecord { message GetBlockInputsResponse { // The latest block header - block_header.BlockHeader block_header = 1; + block.BlockHeader block_header = 1; // Peaks of the above block's mmr, The `forest` value is equal to the block number repeated digest.Digest mmr_peaks = 2; @@ -106,7 +106,7 @@ message GetBlockInputsResponse { repeated NullifierBlockInputRecord nullifiers = 4; // The list of requested notes which were found in the database - repeated digest.Digest found_unauthenticated_notes = 5; + note.NoteAuthenticationInfo found_unauthenticated_notes = 5; } // An account returned as a response to the GetTransactionInputs @@ -136,6 +136,10 @@ message GetNotesByIdResponse { repeated note.Note notes = 1; } +message GetNoteAuthenticationInfoResponse { + note.NoteAuthenticationInfo proofs = 1; +} + message ListNullifiersResponse { // Lists all nullifiers of the current chain repeated smt.SmtLeafEntry nullifiers = 1; diff --git a/crates/rpc-proto/proto/store.proto b/crates/rpc-proto/proto/store.proto index 1563b1261..a9e6fbd85 100644 --- a/crates/rpc-proto/proto/store.proto +++ b/crates/rpc-proto/proto/store.proto @@ -16,6 +16,7 @@ service Api { rpc GetBlockByNumber(requests.GetBlockByNumberRequest) returns (responses.GetBlockByNumberResponse) {} rpc GetBlockHeaderByNumber(requests.GetBlockHeaderByNumberRequest) returns (responses.GetBlockHeaderByNumberResponse) {} rpc GetBlockInputs(requests.GetBlockInputsRequest) returns (responses.GetBlockInputsResponse) {} + rpc GetNoteAuthenticationInfo(requests.GetNoteAuthenticationInfoRequest) returns (responses.GetNoteAuthenticationInfoResponse) {} rpc GetNotesById(requests.GetNotesByIdRequest) returns (responses.GetNotesByIdResponse) {} rpc GetTransactionInputs(requests.GetTransactionInputsRequest) returns (responses.GetTransactionInputsResponse) {} rpc ListAccounts(requests.ListAccountsRequest) returns (responses.ListAccountsResponse) {} diff --git a/crates/rpc-proto/src/proto_files.rs b/crates/rpc-proto/src/proto_files.rs index b53677db4..fb958a9e6 100644 --- a/crates/rpc-proto/src/proto_files.rs +++ b/crates/rpc-proto/src/proto_files.rs @@ -1,7 +1,7 @@ /// A list of tuples containing the names and contents of various protobuf files. pub const PROTO_FILES: &[(&str, &str)] = &[ ("account.proto", include_str!("../proto/account.proto")), - ("block_header.proto", include_str!("../proto/block_header.proto")), + ("block.proto", include_str!("../proto/block.proto")), ("block_producer.proto", include_str!("../proto/block_producer.proto")), ("digest.proto", include_str!("../proto/digest.proto")), ("merkle.proto", include_str!("../proto/merkle.proto")), diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 5cccb6645..c38ceb8f7 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -1,5 +1,5 @@ use std::{ - collections::BTreeSet, + collections::{BTreeMap, BTreeSet}, fs::{self, create_dir_all}, sync::Arc, }; @@ -13,7 +13,7 @@ use miden_objects::{ accounts::AccountDelta, block::{Block, BlockNoteIndex}, crypto::{hash::rpo::RpoDigest, merkle::MerklePath, utils::Deserializable}, - notes::{NoteId, NoteMetadata, Nullifier}, + notes::{NoteId, NoteInclusionProof, NoteMetadata, Nullifier}, transaction::TransactionId, utils::Serializable, BlockHeader, GENESIS_BLOCK, @@ -75,7 +75,7 @@ impl From for NotePb { note_index: note.note_index.to_absolute_index(), note_id: Some(note.note_id.into()), metadata: Some(note.metadata.into()), - merkle_path: Some(note.merkle_path.into()), + merkle_path: Some(Into::into(¬e.merkle_path)), details: note.details, } } @@ -231,13 +231,28 @@ impl Db { })? } + /// Loads multiple block headers from the DB. + #[instrument(target = "miden-store", skip_all, ret(level = "debug"), err)] + pub async fn select_block_headers(&self, blocks: Vec) -> Result> { + self.pool + .get() + .await? + .interact(move |conn| sql::select_block_headers(conn, blocks)) + .await + .map_err(|err| { + DatabaseError::InteractError(format!( + "Select many block headers task failed: {err}" + )) + })? + } + /// Loads all the block headers from the DB. #[instrument(target = "miden-store", skip_all, ret(level = "debug"), err)] - pub async fn select_block_headers(&self) -> Result> { + pub async fn select_all_block_headers(&self) -> Result> { self.pool .get() .await? - .interact(sql::select_block_headers) + .interact(sql::select_all_block_headers) .await .map_err(|err| { DatabaseError::InteractError(format!("Select block headers task failed: {err}")) @@ -321,6 +336,24 @@ impl Db { })? } + /// Loads inclusion proofs for notes matching the given IDs. + #[instrument(target = "miden-store", skip_all, ret(level = "debug"), err)] + pub async fn select_note_inclusion_proofs( + &self, + note_ids: BTreeSet, + ) -> Result> { + self.pool + .get() + .await? + .interact(move |conn| sql::select_note_inclusion_proofs(conn, note_ids)) + .await + .map_err(|err| { + DatabaseError::InteractError(format!( + "Select block note inclusion proofs task failed: {err}" + )) + })? + } + /// Loads all note IDs matching a certain NoteId from the database. #[instrument(target = "miden-store", skip_all, ret(level = "debug"), err)] pub async fn select_note_ids(&self, note_ids: Vec) -> Result> { diff --git a/crates/store/src/db/sql.rs b/crates/store/src/db/sql.rs index 38d4ee751..ae03cb9f4 100644 --- a/crates/store/src/db/sql.rs +++ b/crates/store/src/db/sql.rs @@ -1,13 +1,17 @@ //! Wrapper functions for SQL statements. -use std::{borrow::Cow, rc::Rc}; +use std::{ + borrow::Cow, + collections::{BTreeMap, BTreeSet}, + rc::Rc, +}; use miden_node_proto::domain::accounts::{AccountInfo, AccountSummary}; use miden_objects::{ accounts::{delta::AccountUpdateDetails, Account, AccountDelta}, block::{BlockAccountUpdate, BlockNoteIndex}, crypto::{hash::rpo::RpoDigest, merkle::MerklePath}, - notes::{NoteId, NoteMetadata, NoteType, Nullifier}, + notes::{NoteId, NoteInclusionProof, NoteMetadata, NoteType, Nullifier}, transaction::TransactionId, utils::serde::{Deserializable, Serializable}, BlockHeader, @@ -786,6 +790,58 @@ pub fn select_notes_by_id(conn: &mut Connection, note_ids: &[NoteId]) -> Result< Ok(notes) } +/// Select note inclusion proofs matching the NoteId, using the given [Connection]. +/// +/// # Returns +/// +/// - Empty map if no matching `note`. +/// - Otherwise, note inclusion proofs, which `note_id` matches the `NoteId` as bytes. +pub fn select_note_inclusion_proofs( + conn: &mut Connection, + note_ids: BTreeSet, +) -> Result> { + let note_ids: Vec = note_ids.into_iter().map(|id| id.to_bytes().into()).collect(); + + let mut select_notes_stmt = conn.prepare_cached( + " + SELECT + block_num, + note_id, + batch_index, + note_index, + merkle_path + FROM + notes + WHERE + note_id IN rarray(?1) + ORDER BY + block_num ASC + ", + )?; + + let mut result = BTreeMap::new(); + let mut rows = select_notes_stmt.query(params![Rc::new(note_ids)])?; + while let Some(row) = rows.next()? { + let block_num = row.get(0)?; + + let note_id_data = row.get_ref(1)?.as_blob()?; + let note_id = NoteId::read_from_bytes(note_id_data)?; + + let batch_index = row.get(2)?; + let note_index = row.get(3)?; + let node_index_in_block = BlockNoteIndex::new(batch_index, note_index).to_absolute_index(); + + let merkle_path_data = row.get_ref(4)?.as_blob()?; + let merkle_path = MerklePath::read_from_bytes(merkle_path_data)?; + + let proof = NoteInclusionProof::new(block_num, node_index_in_block, merkle_path)?; + + result.insert(note_id, proof); + } + + Ok(result) +} + // BLOCK CHAIN QUERIES // ================================================================================================ @@ -839,12 +895,41 @@ pub fn select_block_header_by_block_num( } } +/// Select all the given block headers from the DB using the given [Connection]. +/// +/// # Note +/// +/// Only returns the block headers that are actually present. +/// +/// # Returns +/// +/// A vector of [BlockHeader] or an error. +pub fn select_block_headers( + conn: &mut Connection, + blocks: Vec, +) -> Result> { + let mut headers = Vec::with_capacity(blocks.len()); + + let blocks: Vec = blocks.iter().copied().map(u32_to_value).collect(); + let mut stmt = conn + .prepare_cached("SELECT block_header FROM block_headers WHERE block_num IN rarray(?1);")?; + let mut rows = stmt.query(params![Rc::new(blocks)])?; + + while let Some(row) = rows.next()? { + let header = row.get_ref(0)?.as_blob()?; + let header = BlockHeader::read_from_bytes(header)?; + headers.push(header); + } + + Ok(headers) +} + /// Select all block headers from the DB using the given [Connection]. /// /// # Returns /// /// A vector of [BlockHeader] or an error. -pub fn select_block_headers(conn: &mut Connection) -> Result> { +pub fn select_all_block_headers(conn: &mut Connection) -> Result> { let mut stmt = conn.prepare_cached("SELECT block_header FROM block_headers ORDER BY block_num ASC;")?; let mut rows = stmt.query([])?; diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index db47b5932..c18838f80 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -739,7 +739,7 @@ fn test_db_block_header() { let res = sql::select_block_header_by_block_num(&mut conn, None).unwrap(); assert!(res.is_none()); - let res = sql::select_block_headers(&mut conn).unwrap(); + let res = sql::select_all_block_headers(&mut conn).unwrap(); assert!(res.is_empty()); let block_header = BlockHeader::new( @@ -794,7 +794,7 @@ fn test_db_block_header() { let res = sql::select_block_header_by_block_num(&mut conn, None).unwrap(); assert_eq!(res.unwrap(), block_header2); - let res = sql::select_block_headers(&mut conn).unwrap(); + let res = sql::select_all_block_headers(&mut conn).unwrap(); assert_eq!(res, [block_header, block_header2]); } diff --git a/crates/store/src/errors.rs b/crates/store/src/errors.rs index 65461186d..f2aca5c15 100644 --- a/crates/store/src/errors.rs +++ b/crates/store/src/errors.rs @@ -74,6 +74,8 @@ pub enum DatabaseError { expected: RpoDigest, calculated: RpoDigest, }, + #[error("Block {0} not found in the database")] + BlockNotFoundInDb(BlockNumber), #[error("Unsupported database version. There is no migration chain from/to this version. Remove database files \ and try again.")] UnsupportedDatabaseVersion, @@ -203,6 +205,17 @@ pub enum GetBlockInputsError { FailedToGetMmrPeaksForForest { forest: usize, error: MmrError }, #[error("Chain MMR forest expected to be 1 less than latest header's block num. Chain MMR forest: {forest}, block num: {block_num}")] IncorrectChainMmrForestNumber { forest: usize, block_num: u32 }, + #[error("Note inclusion proof MMR error: {0}")] + NoteInclusionMmr(MmrError), +} + +impl From for GetBlockInputsError { + fn from(value: GetNoteInclusionProofError) -> Self { + match value { + GetNoteInclusionProofError::DatabaseError(db_err) => db_err.into(), + GetNoteInclusionProofError::MmrError(mmr_err) => Self::NoteInclusionMmr(mmr_err), + } + } } #[derive(Error, Debug)] @@ -224,3 +237,11 @@ pub enum NoteSyncError { #[error("Error retrieving the merkle proof for the block: {0}")] MmrError(#[from] MmrError), } + +#[derive(Error, Debug)] +pub enum GetNoteInclusionProofError { + #[error("Database error: {0}")] + DatabaseError(#[from] DatabaseError), + #[error("Mmr error: {0}")] + MmrError(#[from] MmrError), +} diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 88fc63469..23dfc8e49 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -6,21 +6,21 @@ use miden_node_proto::{ generated::{ self, account::AccountSummary, - note::NoteSyncRecord, + note::{NoteAuthenticationInfo as NoteAuthenticationInfoProto, NoteSyncRecord}, requests::{ ApplyBlockRequest, CheckNullifiersByPrefixRequest, CheckNullifiersRequest, GetAccountDetailsRequest, GetAccountStateDeltaRequest, GetBlockByNumberRequest, - GetBlockHeaderByNumberRequest, GetBlockInputsRequest, GetNotesByIdRequest, - GetTransactionInputsRequest, ListAccountsRequest, ListNotesRequest, - ListNullifiersRequest, SyncNoteRequest, SyncStateRequest, + GetBlockHeaderByNumberRequest, GetBlockInputsRequest, GetNoteAuthenticationInfoRequest, + GetNotesByIdRequest, GetTransactionInputsRequest, ListAccountsRequest, + ListNotesRequest, ListNullifiersRequest, SyncNoteRequest, SyncStateRequest, }, responses::{ AccountTransactionInputRecord, ApplyBlockResponse, CheckNullifiersByPrefixResponse, CheckNullifiersResponse, GetAccountDetailsResponse, GetAccountStateDeltaResponse, GetBlockByNumberResponse, GetBlockHeaderByNumberResponse, GetBlockInputsResponse, - GetNotesByIdResponse, GetTransactionInputsResponse, ListAccountsResponse, - ListNotesResponse, ListNullifiersResponse, NullifierTransactionInputRecord, - NullifierUpdate, SyncNoteResponse, SyncStateResponse, + GetNoteAuthenticationInfoResponse, GetNotesByIdResponse, GetTransactionInputsResponse, + ListAccountsResponse, ListNotesResponse, ListNullifiersResponse, + NullifierTransactionInputRecord, NullifierUpdate, SyncNoteResponse, SyncStateResponse, }, smt::SmtLeafEntry, store::api_server, @@ -38,7 +38,11 @@ use miden_objects::{ use tonic::{Response, Status}; use tracing::{debug, info, instrument}; -use crate::{state::State, types::AccountId, COMPONENT}; +use crate::{ + state::{NoteAuthenticationInfo, State}, + types::AccountId, + COMPONENT, +}; // STORE API // ================================================================================================ @@ -82,7 +86,7 @@ impl api_server::Api for StoreApi { Ok(Response::new(GetBlockHeaderByNumberResponse { block_header: block_header.map(Into::into), chain_length: mmr_proof.as_ref().map(|p| p.forest as u32), - mmr_path: mmr_proof.map(|p| Into::into(p.merkle_path)), + mmr_path: mmr_proof.map(|p| Into::into(&p.merkle_path)), })) } @@ -196,7 +200,7 @@ impl api_server::Api for StoreApi { note_index: note.note_index.to_absolute_index(), note_id: Some(note.note_id.into()), metadata: Some(note.metadata.into()), - merkle_path: Some(note.merkle_path.into()), + merkle_path: Some(Into::into(¬e.merkle_path)), }) .collect(); @@ -247,14 +251,14 @@ impl api_server::Api for StoreApi { note_index: note.note_index.to_absolute_index(), note_id: Some(note.note_id.into()), metadata: Some(note.metadata.into()), - merkle_path: Some(note.merkle_path.into()), + merkle_path: Some((¬e.merkle_path).into()), }) .collect(); Ok(Response::new(SyncNoteResponse { chain_tip: state.chain_tip, block_header: Some(state.block_header.into()), - mmr_path: Some(mmr_proof.merkle_path.into()), + mmr_path: Some((&mmr_proof.merkle_path).into()), notes, })) } @@ -294,6 +298,42 @@ impl api_server::Api for StoreApi { Ok(Response::new(GetNotesByIdResponse { notes })) } + /// Returns a list of Note inclusion proofs for the specified NoteId's. + #[instrument( + target = "miden-store", + name = "store:get_note_inclusion_proofs", + skip_all, + ret(level = "debug"), + err + )] + async fn get_note_authentication_info( + &self, + request: tonic::Request, + ) -> Result, Status> { + info!(target: COMPONENT, ?request); + + let note_ids = request.into_inner().note_ids; + + let note_ids: Vec = try_convert(note_ids) + .map_err(|err| Status::invalid_argument(format!("Invalid NoteId: {}", err)))?; + + let note_ids = note_ids.into_iter().map(From::from).collect(); + + let NoteAuthenticationInfo { block_proofs, note_proofs } = self + .state + .get_note_authentication_info(note_ids) + .await + .map_err(internal_error)?; + + // Massage into shape required by protobuf + let note_proofs = note_proofs.iter().map(Into::into).collect(); + let block_proofs = block_proofs.into_iter().map(Into::into).collect(); + + Ok(Response::new(GetNoteAuthenticationInfoResponse { + proofs: Some(NoteAuthenticationInfoProto { note_proofs, block_proofs }), + })) + } + /// Returns details for public (on-chain) account by id. #[instrument( target = "miden-store", @@ -377,6 +417,7 @@ impl api_server::Api for StoreApi { let nullifiers = validate_nullifiers(&request.nullifiers)?; let account_ids: Vec = request.account_ids.iter().map(|e| e.id).collect(); let unauthenticated_notes = validate_notes(&request.unauthenticated_notes)?; + let unauthenticated_notes = unauthenticated_notes.into_iter().collect(); self.state .get_block_inputs(&account_ids, &nullifiers, unauthenticated_notes) diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index 88a02241a..fcf790628 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -2,11 +2,21 @@ //! //! The [State] provides data access and modifications methods, its main purpose is to ensure that //! data is atomically written, and that reads are consistent. -use std::{collections::BTreeSet, sync::Arc}; + +use std::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, +}; use miden_node_proto::{ - convert, domain::accounts::AccountInfo, generated::responses::GetBlockInputsResponse, - AccountInputRecord, NullifierWitness, + convert, + domain::{accounts::AccountInfo, blocks::BlockInclusionProof}, + errors::ConversionError, + generated::{ + note::NoteAuthenticationInfo as NoteAuthenticationInfoProto, + responses::GetBlockInputsResponse, + }, + try_convert, AccountInputRecord, NullifierWitness, }; use miden_node_utils::formatting::{format_account_id, format_array}; use miden_objects::{ @@ -14,9 +24,11 @@ use miden_objects::{ block::Block, crypto::{ hash::rpo::RpoDigest, - merkle::{LeafIndex, Mmr, MmrDelta, MmrPeaks, MmrProof, SimpleSmt, SmtProof, ValuePath}, + merkle::{ + LeafIndex, Mmr, MmrDelta, MmrError, MmrPeaks, MmrProof, SimpleSmt, SmtProof, ValuePath, + }, }, - notes::{NoteId, Nullifier}, + notes::{NoteId, NoteInclusionProof, Nullifier}, transaction::OutputNote, utils::Serializable, AccountError, BlockHeader, ACCOUNT_TREE_DEPTH, @@ -31,8 +43,8 @@ use crate::{ blocks::BlockStore, db::{Db, NoteRecord, NoteSyncUpdate, NullifierInfo, StateSyncUpdate}, errors::{ - ApplyBlockError, DatabaseError, GetBlockHeaderError, GetBlockInputsError, NoteSyncError, - StateInitializationError, StateSyncError, + ApplyBlockError, DatabaseError, GetBlockHeaderError, GetBlockInputsError, + GetNoteInclusionProofError, NoteSyncError, StateInitializationError, StateSyncError, }, nullifier_tree::NullifierTree, types::{AccountId, BlockNumber}, @@ -58,7 +70,7 @@ pub struct BlockInputs { pub nullifiers: Vec, /// List of notes found in the store - pub found_unauthenticated_notes: BTreeSet, + pub found_unauthenticated_notes: NoteAuthenticationInfo, } impl From for GetBlockInputsResponse { @@ -68,7 +80,7 @@ impl From for GetBlockInputsResponse { mmr_peaks: convert(value.chain_peaks.peaks()), account_states: convert(value.account_states), nullifiers: convert(value.nullifiers), - found_unauthenticated_notes: convert(value.found_unauthenticated_notes), + found_unauthenticated_notes: Some(value.found_unauthenticated_notes.into()), } } } @@ -87,6 +99,44 @@ struct InnerState { account_tree: SimpleSmt, } +#[derive(Clone, Default, Debug)] +pub struct NoteAuthenticationInfo { + pub block_proofs: Vec, + pub note_proofs: BTreeMap, +} + +impl NoteAuthenticationInfo { + pub fn contains_note(&self, note: &NoteId) -> bool { + self.note_proofs.contains_key(note) + } + + pub fn note_ids(&self) -> BTreeSet { + self.note_proofs.keys().copied().collect() + } +} + +impl From for NoteAuthenticationInfoProto { + fn from(value: NoteAuthenticationInfo) -> Self { + Self { + note_proofs: convert(&value.note_proofs), + block_proofs: convert(value.block_proofs), + } + } +} + +impl TryFrom for NoteAuthenticationInfo { + type Error = ConversionError; + + fn try_from(value: NoteAuthenticationInfoProto) -> Result { + let result = Self { + block_proofs: try_convert(value.block_proofs)?, + note_proofs: try_convert(&value.note_proofs)?, + }; + + Ok(result) + } +} + /// The rollup state pub struct State { /// The database which stores block headers, nullifiers, notes, and the latest states of @@ -399,6 +449,66 @@ impl State { self.db.select_notes_by_id(note_ids).await } + /// Queries all the note inclusion proofs matching a certain Note IDs from the database. + pub async fn get_note_authentication_info( + &self, + note_ids: BTreeSet, + ) -> Result { + // First we grab block-inclusion proofs for the known notes. These proofs only + // prove that the note was included in a given block. We then also need to prove that + // each of those blocks is included in the chain. + let note_proofs = self.db.select_note_inclusion_proofs(note_ids).await?; + + // The set of blocks that the notes are included in. + let blocks = note_proofs + .values() + .map(|proof| proof.location().block_num()) + .collect::>() + .into_iter() + .collect::>(); + + // Grab the block merkle paths from the inner state. + // + // NOTE: Scoped block to automatically drop the mutex guard asap. + // + // We also avoid accessing the db in the block as this would delay + // dropping the guard. + let (chain_length, merkle_paths) = { + let state = self.inner.read().await; + let chain_length = state.chain_mmr.forest(); + + let paths = blocks + .iter() + .map(|&block_num| { + let proof = state.chain_mmr.open(block_num as usize, chain_length)?.merkle_path; + + Ok::<_, MmrError>((block_num, proof)) + }) + .collect::, MmrError>>()?; + + let chain_length = BlockNumber::try_from(chain_length) + .expect("Forest is a chain length so should fit into block number"); + + (chain_length, paths) + }; + + let headers = self.db.select_block_headers(blocks).await?; + let headers = headers + .into_iter() + .map(|header| (header.block_num(), header)) + .collect::>(); + + let mut block_proofs = Vec::with_capacity(merkle_paths.len()); + for (block_num, mmr_path) in merkle_paths { + let block_header = + *headers.get(&block_num).ok_or(DatabaseError::BlockNotFoundInDb(block_num))?; + + block_proofs.push(BlockInclusionProof { block_header, mmr_path, chain_length }); + } + + Ok(NoteAuthenticationInfo { block_proofs, note_proofs }) + } + /// Loads data to synchronize a client. /// /// The client's request contains a list of tag prefixes, this method will return the first @@ -486,7 +596,7 @@ impl State { &self, account_ids: &[AccountId], nullifiers: &[Nullifier], - unauthenticated_notes: Vec, + unauthenticated_notes: BTreeSet, ) -> Result { let inner = self.inner.read().await; @@ -535,7 +645,8 @@ impl State { }) .collect(); - let found_unauthenticated_notes = self.db.select_note_ids(unauthenticated_notes).await?; + let found_unauthenticated_notes = + self.get_note_authentication_info(unauthenticated_notes).await?; Ok(BlockInputs { block_header: latest, @@ -663,7 +774,7 @@ async fn load_nullifier_tree(db: &mut Db) -> Result Result { let block_hashes: Vec = - db.select_block_headers().await?.iter().map(BlockHeader::hash).collect(); + db.select_all_block_headers().await?.iter().map(BlockHeader::hash).collect(); Ok(block_hashes.into()) } diff --git a/proto/block_header.proto b/proto/block.proto similarity index 81% rename from proto/block_header.proto rename to proto/block.proto index fad1b6c40..85117e751 100644 --- a/proto/block_header.proto +++ b/proto/block.proto @@ -1,7 +1,8 @@ syntax = "proto3"; -package block_header; +package block; import "digest.proto"; +import "merkle.proto"; message BlockHeader { // specifies the version of the protocol. @@ -25,3 +26,10 @@ message BlockHeader { // the time when the block was created. fixed32 timestamp = 10; } + +message BlockInclusionProof { + BlockHeader block_header = 1; + merkle.MerklePath mmr_path = 2; + // The chain length associated with `mmr_path`. + fixed32 chain_length = 3; +} diff --git a/proto/note.proto b/proto/note.proto index f67628690..24d7579a2 100644 --- a/proto/note.proto +++ b/proto/note.proto @@ -1,9 +1,10 @@ syntax = "proto3"; package note; +import "account.proto"; +import "block.proto"; import "digest.proto"; import "merkle.proto"; -import "account.proto"; message NoteMetadata { account.AccountId sender = 1; @@ -24,9 +25,23 @@ message Note { optional bytes details = 6; } +message NoteInclusionInBlockProof { + digest.Digest note_id = 1; + fixed32 block_num = 2; + uint32 note_index_in_block = 3; + merkle.MerklePath merkle_path = 4; +} + message NoteSyncRecord { uint32 note_index = 1; digest.Digest note_id = 2; NoteMetadata metadata = 3; merkle.MerklePath merkle_path = 4; } + +message NoteAuthenticationInfo { + // Proof of each note's inclusion in a block. + repeated note.NoteInclusionInBlockProof note_proofs = 1; + // Proof of each block's inclusion in the chain. + repeated block.BlockInclusionProof block_proofs = 2; +} diff --git a/proto/requests.proto b/proto/requests.proto index 02222f994..07dbe0752 100644 --- a/proto/requests.proto +++ b/proto/requests.proto @@ -2,7 +2,6 @@ syntax = "proto3"; package requests; import "account.proto"; -import "block_header.proto"; import "digest.proto"; import "note.proto"; @@ -100,6 +99,11 @@ message GetNotesByIdRequest { repeated digest.Digest note_ids = 1; } +message GetNoteAuthenticationInfoRequest { + // List of NoteId's to be queried from the database + repeated digest.Digest note_ids = 1; +} + message ListNullifiersRequest {} message ListAccountsRequest {} diff --git a/proto/responses.proto b/proto/responses.proto index ae9b8d99e..65b8a4e5c 100644 --- a/proto/responses.proto +++ b/proto/responses.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package responses; import "account.proto"; -import "block_header.proto"; +import "block.proto"; import "digest.proto"; import "merkle.proto"; import "mmr.proto"; @@ -24,7 +24,7 @@ message CheckNullifiersByPrefixResponse { message GetBlockHeaderByNumberResponse { // The requested block header - block_header.BlockHeader block_header = 1; + block.BlockHeader block_header = 1; // Merkle path to verify the block's inclusion in the MMR at the returned `chain_length` optional merkle.MerklePath mmr_path = 2; @@ -43,7 +43,7 @@ message SyncStateResponse { fixed32 chain_tip = 1; // Block header of the block with the first note matching the specified criteria - block_header.BlockHeader block_header = 2; + block.BlockHeader block_header = 2; // Data needed to update the partial MMR from `request.block_num + 1` to `response.block_header.block_num` mmr.MmrDelta mmr_delta = 3; @@ -67,7 +67,7 @@ message SyncNoteResponse { fixed32 chain_tip = 1; // Block header of the block with the first note matching the specified criteria - block_header.BlockHeader block_header = 2; + block.BlockHeader block_header = 2; // Merkle path to verify the block's inclusion in the MMR at the returned `chain_tip`. // @@ -94,7 +94,7 @@ message NullifierBlockInputRecord { message GetBlockInputsResponse { // The latest block header - block_header.BlockHeader block_header = 1; + block.BlockHeader block_header = 1; // Peaks of the above block's mmr, The `forest` value is equal to the block number repeated digest.Digest mmr_peaks = 2; @@ -106,7 +106,7 @@ message GetBlockInputsResponse { repeated NullifierBlockInputRecord nullifiers = 4; // The list of requested notes which were found in the database - repeated digest.Digest found_unauthenticated_notes = 5; + note.NoteAuthenticationInfo found_unauthenticated_notes = 5; } // An account returned as a response to the GetTransactionInputs @@ -136,6 +136,10 @@ message GetNotesByIdResponse { repeated note.Note notes = 1; } +message GetNoteAuthenticationInfoResponse { + note.NoteAuthenticationInfo proofs = 1; +} + message ListNullifiersResponse { // Lists all nullifiers of the current chain repeated smt.SmtLeafEntry nullifiers = 1; diff --git a/proto/store.proto b/proto/store.proto index 1563b1261..a9e6fbd85 100644 --- a/proto/store.proto +++ b/proto/store.proto @@ -16,6 +16,7 @@ service Api { rpc GetBlockByNumber(requests.GetBlockByNumberRequest) returns (responses.GetBlockByNumberResponse) {} rpc GetBlockHeaderByNumber(requests.GetBlockHeaderByNumberRequest) returns (responses.GetBlockHeaderByNumberResponse) {} rpc GetBlockInputs(requests.GetBlockInputsRequest) returns (responses.GetBlockInputsResponse) {} + rpc GetNoteAuthenticationInfo(requests.GetNoteAuthenticationInfoRequest) returns (responses.GetNoteAuthenticationInfoResponse) {} rpc GetNotesById(requests.GetNotesByIdRequest) returns (responses.GetNotesByIdResponse) {} rpc GetTransactionInputs(requests.GetTransactionInputsRequest) returns (responses.GetTransactionInputsResponse) {} rpc ListAccounts(requests.ListAccountsRequest) returns (responses.ListAccountsResponse) {}