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 1 commit
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).

### Fixes

Expand Down
21 changes: 21 additions & 0 deletions crates/proto/src/generated/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ pub struct SyncStateRequest {
#[prost(uint32, repeated, tag = "4")]
pub nullifiers: ::prost::alloc::vec::Vec<u32>,
}
/// State synchronization request.
///
/// Specifies state updates the client is intersted in. The server will return the first block which
/// contains a note matching `note_tags` or the chain tip. And the corresponding updates to
/// `nullifiers` and `account_ids` for that block range.
#[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 the data starting from the next block,
/// until the
#[prost(fixed32, tag = "1")]
pub block_num: u32,
/// 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")]
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
16 changes: 16 additions & 0 deletions crates/proto/src/generated/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ 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>,
/// 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<super::mmr::MmrDelta>,
/// 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
19 changes: 19 additions & 0 deletions crates/rpc-proto/proto/requests.proto
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ message SyncStateRequest {
repeated uint32 nullifiers = 4;
}

// State synchronization request.
//
// Specifies state updates the client is intersted in. The server will return the first block which
// contains a note matching `note_tags` or the chain tip. And the corresponding updates to
// `nullifiers` and `account_ids` for that block range.
message SyncNoteRequest {
// Last block known by the client. The response will contain the data starting from the next block,
// until the
fixed32 block_num = 1;

// 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;
}

message GetBlockInputsRequest {
// ID of the account against which a transaction is executed.
repeated account.AccountId account_ids = 1;
Expand Down
14 changes: 14 additions & 0 deletions crates/rpc-proto/proto/responses.proto
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ 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;

// Data needed to update the partial MMR from `request.block_num + 1` to `response.block_header.block_num`
mmr.MmrDelta mmr_delta = 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