Skip to content
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 @@ -5,6 +5,7 @@
### Changes

- [BREAKING] Updated MSRV to 1.89.
- Added `GetNoteScriptByRoot` gRPC endpoint for retrieving a note script by its root ([#1196](https://github.com/0xMiden/miden-node/pull/1196)).

## v0.11.0 (2025-08-28)

Expand Down
13 changes: 13 additions & 0 deletions crates/proto/src/domain/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use miden_objects::note::{
NoteId,
NoteInclusionProof,
NoteMetadata,
NoteScript,
NoteTag,
NoteType,
Nullifier,
Expand Down Expand Up @@ -349,3 +350,15 @@ pub enum NetworkNoteError {
#[error("note tag {0} is not a valid network note tag")]
InvalidExecutionMode(NoteTag),
}

// NOTE SCRIPT
// ================================================================================================

impl From<NoteScript> for proto::note::NoteScript {
fn from(script: NoteScript) -> Self {
Self {
entrypoint: script.entrypoint().into(),
mast: script.mast().to_bytes(),
}
}
}
17 changes: 17 additions & 0 deletions crates/proto/src/generated/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,20 @@ pub struct NoteSyncRecord {
#[prost(message, optional, tag = "4")]
pub inclusion_path: ::core::option::Option<super::primitives::SparseMerklePath>,
}
/// Represents a note root.
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct NoteRoot {
/// The root of the note.
#[prost(message, optional, tag = "1")]
pub root: ::core::option::Option<super::primitives::Digest>,
}
/// Represents a note script.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NoteScript {
/// Entrypoint of the script.
#[prost(uint32, tag = "1")]
pub entrypoint: u32,
/// Mast of the script.
#[prost(bytes = "vec", tag = "2")]
pub mast: ::prost::alloc::vec::Vec<u8>,
}
78 changes: 78 additions & 0 deletions crates/proto/src/generated/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,31 @@ pub mod api_client {
req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "GetNotesById"));
self.inner.unary(req, path, codec).await
}
/// Returns the script for a note by its root.
pub async fn get_note_script_by_root(
&mut self,
request: impl tonic::IntoRequest<super::super::note::NoteRoot>,
) -> std::result::Result<
tonic::Response<super::super::rpc_store::MaybeNoteScript>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/rpc.Api/GetNoteScriptByRoot",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("rpc.Api", "GetNoteScriptByRoot"));
self.inner.unary(req, path, codec).await
}
/// Submits proven transaction to the Miden network.
pub async fn submit_proven_transaction(
&mut self,
Expand Down Expand Up @@ -558,6 +583,14 @@ pub mod api_server {
tonic::Response<super::super::note::CommittedNoteList>,
tonic::Status,
>;
/// Returns the script for a note by its root.
async fn get_note_script_by_root(
&self,
request: tonic::Request<super::super::note::NoteRoot>,
) -> std::result::Result<
tonic::Response<super::super::rpc_store::MaybeNoteScript>,
tonic::Status,
>;
/// Submits proven transaction to the Miden network.
async fn submit_proven_transaction(
&self,
Expand Down Expand Up @@ -1086,6 +1119,51 @@ pub mod api_server {
};
Box::pin(fut)
}
"/rpc.Api/GetNoteScriptByRoot" => {
#[allow(non_camel_case_types)]
struct GetNoteScriptByRootSvc<T: Api>(pub Arc<T>);
impl<
T: Api,
> tonic::server::UnaryService<super::super::note::NoteRoot>
for GetNoteScriptByRootSvc<T> {
type Response = super::super::rpc_store::MaybeNoteScript;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::super::note::NoteRoot>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Api>::get_note_script_by_root(&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 method = GetNoteScriptByRootSvc(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/SubmitProvenTransaction" => {
#[allow(non_camel_case_types)]
struct SubmitProvenTransactionSvc<T: Api>(pub Arc<T>);
Expand Down
82 changes: 82 additions & 0 deletions crates/proto/src/generated/rpc_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,13 @@ pub struct StorageMapUpdate {
#[prost(message, optional, tag = "4")]
pub value: ::core::option::Option<super::primitives::Digest>,
}
/// Represents a note script or nothing.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MaybeNoteScript {
/// The script for a note by its root.
#[prost(message, optional, tag = "1")]
pub script: ::core::option::Option<super::note::NoteScript>,
}
/// Generated client implementations.
pub mod rpc_client {
#![allow(
Expand Down Expand Up @@ -641,6 +648,31 @@ pub mod rpc_client {
.insert(GrpcMethod::new("rpc_store.Rpc", "GetNotesById"));
self.inner.unary(req, path, codec).await
}
/// Returns the script for a note by its root.
pub async fn get_note_script_by_root(
&mut self,
request: impl tonic::IntoRequest<super::super::note::NoteRoot>,
) -> std::result::Result<
tonic::Response<super::MaybeNoteScript>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/rpc_store.Rpc/GetNoteScriptByRoot",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("rpc_store.Rpc", "GetNoteScriptByRoot"));
self.inner.unary(req, path, codec).await
}
/// Returns info which can be used by the client to sync up to the tip of chain for the notes they are interested in.
///
/// Client specifies the `note_tags` they are interested in, and the block height from which to search for new for
Expand Down Expand Up @@ -833,6 +865,11 @@ pub mod rpc_server {
tonic::Response<super::super::note::CommittedNoteList>,
tonic::Status,
>;
/// Returns the script for a note by its root.
async fn get_note_script_by_root(
&self,
request: tonic::Request<super::super::note::NoteRoot>,
) -> std::result::Result<tonic::Response<super::MaybeNoteScript>, tonic::Status>;
/// Returns info which can be used by the client to sync up to the tip of chain for the notes they are interested in.
///
/// Client specifies the `note_tags` they are interested in, and the block height from which to search for new for
Expand Down Expand Up @@ -1324,6 +1361,51 @@ pub mod rpc_server {
};
Box::pin(fut)
}
"/rpc_store.Rpc/GetNoteScriptByRoot" => {
#[allow(non_camel_case_types)]
struct GetNoteScriptByRootSvc<T: Rpc>(pub Arc<T>);
impl<
T: Rpc,
> tonic::server::UnaryService<super::super::note::NoteRoot>
for GetNoteScriptByRootSvc<T> {
type Response = super::MaybeNoteScript;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::super::note::NoteRoot>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Rpc>::get_note_script_by_root(&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 method = GetNoteScriptByRootSvc(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_store.Rpc/SyncNotes" => {
#[allow(non_camel_case_types)]
struct SyncNotesSvc<T: Rpc>(pub Arc<T>);
Expand Down
7 changes: 7 additions & 0 deletions crates/rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The full gRPC method definitions can be found in the [proto](../proto/README.md)
- [GetBlockByNumber](#getblockbynumber)
- [GetBlockHeaderByNumber](#getblockheaderbynumber)
- [GetNotesById](#getnotesbyid)
- [GetNoteScriptByRoot](#getnotescriptbyroot)
- [SubmitProvenTransaction](#submitproventransaction)
- [SyncAccountVault](#SyncAccountVault)
- [SyncNotes](#syncnotes)
Expand Down Expand Up @@ -76,6 +77,12 @@ Returns a list of notes matching the provided note IDs.

---

### GetNoteScriptByRoot

Returns the script for a note by its root.

---

### SubmitProvenTransaction

Submits proven transaction to the Miden network.
Expand Down
17 changes: 17 additions & 0 deletions crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,23 @@ impl api_server::Api for RpcService {
genesis_commitment: self.genesis_commitment.map(Into::into),
}))
}

#[instrument(
parent = None,
target = COMPONENT,
name = "rpc.server.get_note_script_by_root",
skip_all,
ret(level = "debug"),
err
)]
async fn get_note_script_by_root(
&self,
request: Request<proto::note::NoteRoot>,
) -> Result<Response<proto::rpc_store::MaybeNoteScript>, Status> {
debug!(target: COMPONENT, request = ?request);

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

// LIMIT HELPERS
Expand Down
7 changes: 7 additions & 0 deletions crates/store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The full gRPC API can be found [here](../../proto/proto/store.proto).
- [GetNoteAuthenticationInfo](#getnoteauthenticationinfo)
- [GetNotesById](#getnotesbyid)
- [GetTransactionInputs](#gettransactioninputs)
- [GetNoteScriptByRoot](#getnotescriptbyroot)
- [SyncAccountVault](#syncaccountvault)
- [SyncNotes](#syncnotes)
- [SyncState](#syncstate)
Expand Down Expand Up @@ -102,6 +103,12 @@ Used by the `block-producer` to query state required to verify a submitted trans

---

### GetNoteScriptByRoot

Returns the script for a note by its root.

---

### SyncAccountVault

Returns information that allows clients to sync asset values for specific public accounts within a block range.
Expand Down
17 changes: 16 additions & 1 deletion crates/store/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ use miden_objects::account::AccountId;
use miden_objects::asset::Asset;
use miden_objects::block::{BlockHeader, BlockNoteIndex, BlockNumber, ProvenBlock};
use miden_objects::crypto::merkle::SparseMerklePath;
use miden_objects::note::{NoteDetails, NoteId, NoteInclusionProof, NoteMetadata, Nullifier};
use miden_objects::note::{
NoteDetails,
NoteId,
NoteInclusionProof,
NoteMetadata,
NoteScript,
Nullifier,
};
use miden_objects::transaction::TransactionId;
use tokio::sync::oneshot;
use tracing::{info, info_span, instrument};
Expand Down Expand Up @@ -538,4 +545,12 @@ impl Db {
})
.await
}

/// Returns the script for a note by its root.
pub async fn select_note_script_by_root(&self, root: Word) -> Result<Option<NoteScript>> {
self.transact("note script by root", move |conn| {
queries::select_note_script_by_root(conn, root)
})
.await
}
}
28 changes: 27 additions & 1 deletion crates/store/src/db/models/queries/notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use diesel::query_dsl::methods::SelectDsl;
use diesel::query_dsl::{QueryDsl, RunQueryDsl};
use diesel::sqlite::Sqlite;
use diesel::{JoinOnDsl, NullableExpressionMethods, OptionalExtension, SqliteConnection};
use miden_lib::utils::Deserializable;
use miden_lib::utils::{Deserializable, Serializable};
use miden_node_utils::limiter::{
QueryParamAccountIdLimit,
QueryParamLimiter,
Expand Down Expand Up @@ -250,6 +250,32 @@ pub(crate) fn select_note_inclusion_proofs(
))
}

/// Returns the script for a note by its root.
///
/// ```sql
/// SELECT
/// root,
/// script
/// FROM
/// note_scripts
/// WHERE
/// root = ?1;
/// ```
pub(crate) fn select_note_script_by_root(
conn: &mut SqliteConnection,
root: Word,
) -> Result<Option<NoteScript>, DatabaseError> {
let raw = SelectDsl::select(schema::note_scripts::table, schema::note_scripts::script)
.filter(schema::note_scripts::script_root.eq(root.to_bytes()))
.get_result::<Vec<u8>>(conn)
.optional()?;

raw.as_ref()
.map(|bytes| NoteScript::from_bytes(bytes))
.transpose()
.map_err(Into::into)
}

/// Returns a paginated batch of network notes that have not yet been consumed.
///
/// # Returns
Expand Down
Loading