Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): SyncNotes endpoint #424

Merged
merged 9 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Added `GetAccountStateDelta` endpoint (#418).
- Added `CheckNullifiersByPrefix` endpoint (#419).
- Support multiple inflight transactions on the same account (#407).
- Added `SyncNotes` endpoint (#424).
- Cache sql statements (#427).

### Fixes
Expand Down
1 change: 1 addition & 0 deletions crates/block-producer/src/batch_builder/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ impl TransactionBatch {

/// Returns an iterator over (account_id, init_state_hash) tuples for accounts that were
/// modified in this transaction batch.
#[cfg(test)]
pub fn account_initial_states(&self) -> impl Iterator<Item = (AccountId, Digest)> + '_ {
self.updated_accounts
.iter()
Expand Down
29 changes: 19 additions & 10 deletions crates/proto/src/generated/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,29 @@ pub struct SyncStateRequest {
/// it won't be included in the response.
#[prost(message, repeated, tag = "2")]
pub account_ids: ::prost::alloc::vec::Vec<super::account::AccountId>,
/// Determines the tags which the client is interested in. These are only the 16high bits of the
/// note's complete tag.
///
/// The above means it is not possible to request an specific note, but only a "note family",
/// this is done to increase the privacy of the client, by hiding the note's the client is
/// intereted on.
#[prost(uint32, repeated, tag = "3")]
/// Specifies the tags which the client is interested in.
#[prost(fixed32, repeated, tag = "3")]
pub note_tags: ::prost::alloc::vec::Vec<u32>,
/// Determines the nullifiers the client is interested in.
///
/// Similarly to the note_tags, this determins only the 16high bits of the target nullifier.
/// Determines the nullifiers the client is interested in by specifying the 16high bits of the
/// target nullifier.
#[prost(uint32, repeated, tag = "4")]
pub nullifiers: ::prost::alloc::vec::Vec<u32>,
}
/// Note synchronization request.
///
/// Specifies note tags that client is intersted in. The server will return the first block which
/// contains a note matching `note_tags` or the chain tip.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SyncNoteRequest {
/// Last block known by the client. The response will contain data starting from the next block,
/// until the first block which contains a note of matching the requested tag.
#[prost(fixed32, tag = "1")]
pub block_num: u32,
/// Specifies the tags which the client is interested in.
#[prost(fixed32, repeated, tag = "2")]
pub note_tags: ::prost::alloc::vec::Vec<u32>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetBlockInputsRequest {
Expand Down
21 changes: 21 additions & 0 deletions crates/proto/src/generated/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ pub struct SyncStateResponse {
#[prost(message, repeated, tag = "8")]
pub nullifiers: ::prost::alloc::vec::Vec<NullifierUpdate>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SyncNoteResponse {
/// Number of the latest block in the chain
#[prost(fixed32, tag = "1")]
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<super::block_header::BlockHeader>,
/// Proof for block header's MMR with respect to the chain tip.
///
/// More specifically, the full proof consists of `forest`, `position` and `path` components. This
/// value constitutes the `path`. The other two components can be obtained as follows:
/// - `position` is simply `resopnse.block_header.block_num`
/// - `forest` is the same as `response.chain_tip + 1`
#[prost(message, optional, tag = "3")]
pub mmr_path: ::core::option::Option<super::merkle::MerklePath>,
/// List of all notes together with the Merkle paths from `response.block_header.note_root`
#[prost(message, repeated, tag = "4")]
pub notes: ::prost::alloc::vec::Vec<super::note::NoteSyncRecord>,
}
/// An account returned as a response to the GetBlockInputs
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down
78 changes: 78 additions & 0 deletions crates/proto/src/generated/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,28 @@ pub mod api_client {
.insert(GrpcMethod::new("rpc.Api", "SubmitProvenTransaction"));
self.inner.unary(req, path, codec).await
}
pub async fn sync_notes(
&mut self,
request: impl tonic::IntoRequest<super::super::requests::SyncNoteRequest>,
) -> std::result::Result<
tonic::Response<super::super::responses::SyncNoteResponse>,
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("/rpc.Api/SyncNotes");
let mut req = request.into_request();
req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "SyncNotes"));
self.inner.unary(req, path, codec).await
}
pub async fn sync_state(
&mut self,
request: impl tonic::IntoRequest<super::super::requests::SyncStateRequest>,
Expand Down Expand Up @@ -381,6 +403,13 @@ pub mod api_server {
tonic::Response<super::super::responses::SubmitProvenTransactionResponse>,
tonic::Status,
>;
async fn sync_notes(
&self,
request: tonic::Request<super::super::requests::SyncNoteRequest>,
) -> std::result::Result<
tonic::Response<super::super::responses::SyncNoteResponse>,
tonic::Status,
>;
async fn sync_state(
&self,
request: tonic::Request<super::super::requests::SyncStateRequest>,
Expand Down Expand Up @@ -862,6 +891,55 @@ pub mod api_server {
};
Box::pin(fut)
}
"/rpc.Api/SyncNotes" => {
#[allow(non_camel_case_types)]
struct SyncNotesSvc<T: Api>(pub Arc<T>);
impl<
T: Api,
> tonic::server::UnaryService<
super::super::requests::SyncNoteRequest,
> for SyncNotesSvc<T> {
type Response = super::super::responses::SyncNoteResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<
super::super::requests::SyncNoteRequest,
>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Api>::sync_notes(&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 = SyncNotesSvc(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)
}
"/rpc.Api/SyncState" => {
#[allow(non_camel_case_types)]
struct SyncStateSvc<T: Api>(pub Arc<T>);
Expand Down
78 changes: 78 additions & 0 deletions crates/proto/src/generated/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,28 @@ pub mod api_client {
req.extensions_mut().insert(GrpcMethod::new("store.Api", "ListNullifiers"));
self.inner.unary(req, path, codec).await
}
pub async fn sync_notes(
&mut self,
request: impl tonic::IntoRequest<super::super::requests::SyncNoteRequest>,
) -> std::result::Result<
tonic::Response<super::super::responses::SyncNoteResponse>,
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/SyncNotes");
let mut req = request.into_request();
req.extensions_mut().insert(GrpcMethod::new("store.Api", "SyncNotes"));
self.inner.unary(req, path, codec).await
}
pub async fn sync_state(
&mut self,
request: impl tonic::IntoRequest<super::super::requests::SyncStateRequest>,
Expand Down Expand Up @@ -534,6 +556,13 @@ pub mod api_server {
tonic::Response<super::super::responses::ListNullifiersResponse>,
tonic::Status,
>;
async fn sync_notes(
&self,
request: tonic::Request<super::super::requests::SyncNoteRequest>,
) -> std::result::Result<
tonic::Response<super::super::responses::SyncNoteResponse>,
tonic::Status,
>;
async fn sync_state(
&self,
request: tonic::Request<super::super::requests::SyncStateRequest>,
Expand Down Expand Up @@ -1260,6 +1289,55 @@ pub mod api_server {
};
Box::pin(fut)
}
"/store.Api/SyncNotes" => {
#[allow(non_camel_case_types)]
struct SyncNotesSvc<T: Api>(pub Arc<T>);
impl<
T: Api,
> tonic::server::UnaryService<
super::super::requests::SyncNoteRequest,
> for SyncNotesSvc<T> {
type Response = super::super::responses::SyncNoteResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<
super::super::requests::SyncNoteRequest,
>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Api>::sync_notes(&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 = SyncNotesSvc(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/SyncState" => {
#[allow(non_camel_case_types)]
struct SyncStateSvc<T: Api>(pub Arc<T>);
Expand Down
27 changes: 17 additions & 10 deletions crates/rpc-proto/proto/requests.proto
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,27 @@ message SyncStateRequest {
// it won't be included in the response.
repeated account.AccountId account_ids = 2;

// Determines the tags which the client is interested in. These are only the 16high bits of the
// note's complete tag.
//
// The above means it is not possible to request an specific note, but only a "note family",
// this is done to increase the privacy of the client, by hiding the note's the client is
// intereted on.
repeated uint32 note_tags = 3;
// Specifies the tags which the client is interested in.
repeated fixed32 note_tags = 3;

// Determines the nullifiers the client is interested in.
//
// Similarly to the note_tags, this determins only the 16high bits of the target nullifier.
// Determines the nullifiers the client is interested in by specifying the 16high bits of the
// target nullifier.
repeated uint32 nullifiers = 4;
}

// Note synchronization request.
//
// Specifies note tags that client is intersted in. The server will return the first block which
// contains a note matching `note_tags` or the chain tip.
message SyncNoteRequest {
// Last block known by the client. The response will contain data starting from the next block,
// until the first block which contains a note of matching the requested tag.
fixed32 block_num = 1;

// Specifies the tags which the client is interested in.
repeated fixed32 note_tags = 2;
}

message GetBlockInputsRequest {
// ID of the account against which a transaction is executed.
repeated account.AccountId account_ids = 1;
Expand Down
19 changes: 19 additions & 0 deletions crates/rpc-proto/proto/responses.proto
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,25 @@ message SyncStateResponse {
repeated NullifierUpdate nullifiers = 8;
}

message SyncNoteResponse {
// Number of the latest block in the chain
fixed32 chain_tip = 1;

// Block header of the block with the first note matching the specified criteria
block_header.BlockHeader block_header = 2;

// Proof for block header's MMR with respect to the chain tip.
//
// More specifically, the full proof consists of `forest`, `position` and `path` components. This
// value constitutes the `path`. The other two components can be obtained as follows:
// - `position` is simply `resopnse.block_header.block_num`
// - `forest` is the same as `response.chain_tip + 1`
merkle.MerklePath mmr_path = 3;

// List of all notes together with the Merkle paths from `response.block_header.note_root`
repeated note.NoteSyncRecord notes = 4;
}

// An account returned as a response to the GetBlockInputs
message AccountBlockInputRecord {
account.AccountId account_id = 1;
Expand Down
1 change: 1 addition & 0 deletions crates/rpc-proto/proto/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ service Api {
rpc GetBlockHeaderByNumber(requests.GetBlockHeaderByNumberRequest) returns (responses.GetBlockHeaderByNumberResponse) {}
rpc GetNotesById(requests.GetNotesByIdRequest) returns (responses.GetNotesByIdResponse) {}
rpc SubmitProvenTransaction(requests.SubmitProvenTransactionRequest) returns (responses.SubmitProvenTransactionResponse) {}
rpc SyncNotes(requests.SyncNoteRequest) returns (responses.SyncNoteResponse) {}
rpc SyncState(requests.SyncStateRequest) returns (responses.SyncStateResponse) {}
}
1 change: 1 addition & 0 deletions crates/rpc-proto/proto/store.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ service Api {
rpc ListAccounts(requests.ListAccountsRequest) returns (responses.ListAccountsResponse) {}
rpc ListNotes(requests.ListNotesRequest) returns (responses.ListNotesResponse) {}
rpc ListNullifiers(requests.ListNullifiersRequest) returns (responses.ListNullifiersResponse) {}
rpc SyncNotes(requests.SyncNoteRequest) returns (responses.SyncNoteResponse) {}
rpc SyncState(requests.SyncStateRequest) returns (responses.SyncStateResponse) {}
}
21 changes: 19 additions & 2 deletions crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ use miden_node_proto::{
requests::{
CheckNullifiersByPrefixRequest, CheckNullifiersRequest, GetAccountDetailsRequest,
GetAccountStateDeltaRequest, GetBlockByNumberRequest, GetBlockHeaderByNumberRequest,
GetNotesByIdRequest, SubmitProvenTransactionRequest, SyncStateRequest,
GetNotesByIdRequest, SubmitProvenTransactionRequest, SyncNoteRequest, SyncStateRequest,
},
responses::{
CheckNullifiersByPrefixResponse, CheckNullifiersResponse, GetAccountDetailsResponse,
GetAccountStateDeltaResponse, GetBlockByNumberResponse, GetBlockHeaderByNumberResponse,
GetNotesByIdResponse, SubmitProvenTransactionResponse, SyncStateResponse,
GetNotesByIdResponse, SubmitProvenTransactionResponse, SyncNoteResponse,
SyncStateResponse,
},
rpc::api_server,
store::api_client as store_client,
Expand Down Expand Up @@ -130,6 +131,22 @@ impl api_server::Api for RpcApi {
self.store.clone().sync_state(request).await
}

#[instrument(
target = "miden-rpc",
name = "rpc:sync_notes",
skip_all,
ret(level = "debug"),
err
)]
async fn sync_notes(
&self,
request: Request<SyncNoteRequest>,
) -> Result<Response<SyncNoteResponse>, Status> {
debug!(target: COMPONENT, request = ?request.get_ref());

self.store.clone().sync_notes(request).await
}

#[instrument(
target = "miden-rpc",
name = "rpc:get_notes_by_id",
Expand Down
Loading
Loading