Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -25,6 +25,7 @@
- Improved tracing span fields ([#1650](https://github.com/0xMiden/miden-node/pull/1650))
- Replaced NTX Builder's in-memory state management with SQLite-backed persistence; account states, notes, and transaction effects are now stored in the database and inflight state is purged on startup ([#1662](https://github.com/0xMiden/miden-node/pull/1662)).
- [BREAKING] Reworked `miden-remote-prover`, removing the `worker`/`proxy` distinction and simplifying to a `worker` with a request queue ([#1688](https://github.com/0xMiden/miden-node/pull/1688)).
- Cleaned up RPC `api.rs` with section separators, method grouping, doc comments, and extracted duplicated decorator stripping logic ([#1713](https://github.com/0xMiden/miden-node/pull/1713)).
Comment thread
Mirko-von-Leipzig marked this conversation as resolved.
Outdated

## v0.13.7 (2026-02-25)

Expand Down
200 changes: 109 additions & 91 deletions crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,13 @@ impl RpcService {
}
}

// API IMPLEMENTATION
// ================================================================================================

#[tonic::async_trait]
impl api_server::Api for RpcService {
// -- Nullifier endpoints -----------------------------------------------------------------

async fn check_nullifiers(
&self,
request: Request<proto::rpc::NullifierList>,
Expand Down Expand Up @@ -183,6 +188,8 @@ impl api_server::Api for RpcService {
self.store.clone().sync_nullifiers(request).await
}

// -- Block endpoints ---------------------------------------------------------------------

async fn get_block_header_by_number(
&self,
request: Request<proto::rpc::BlockHeaderByNumberRequest>,
Expand All @@ -192,6 +199,17 @@ impl api_server::Api for RpcService {
self.store.clone().get_block_header_by_number(request).await
}

async fn get_block_by_number(
&self,
request: Request<proto::blockchain::BlockNumber>,
) -> Result<Response<proto::blockchain::MaybeBlock>, Status> {
let request = request.into_inner();

debug!(target: COMPONENT, ?request);

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

async fn sync_chain_mmr(
&self,
request: Request<proto::rpc::SyncChainMmrRequest>,
Expand All @@ -201,14 +219,7 @@ impl api_server::Api for RpcService {
self.store.clone().sync_chain_mmr(request).await
}

async fn sync_account_storage_maps(
&self,
request: Request<proto::rpc::SyncAccountStorageMapsRequest>,
) -> Result<Response<proto::rpc::SyncAccountStorageMapsResponse>, Status> {
debug!(target: COMPONENT, request = ?request.get_ref());

self.store.clone().sync_account_storage_maps(request).await
}
// -- Note endpoints ----------------------------------------------------------------------

async fn sync_notes(
&self,
Expand Down Expand Up @@ -242,6 +253,26 @@ impl api_server::Api for RpcService {
self.store.clone().get_notes_by_id(request).await
}

async fn get_note_script_by_root(
&self,
request: Request<proto::note::NoteRoot>,
) -> Result<Response<proto::rpc::MaybeNoteScript>, Status> {
debug!(target: COMPONENT, request = ?request);

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

// -- Account endpoints -------------------------------------------------------------------

async fn sync_account_storage_maps(
&self,
request: Request<proto::rpc::SyncAccountStorageMapsRequest>,
) -> Result<Response<proto::rpc::SyncAccountStorageMapsResponse>, Status> {
debug!(target: COMPONENT, request = ?request.get_ref());

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

async fn sync_account_vault(
&self,
request: tonic::Request<proto::rpc::SyncAccountVaultRequest>,
Expand All @@ -252,6 +283,41 @@ impl api_server::Api for RpcService {
self.store.clone().sync_account_vault(request).await
}

/// Validates storage map key limits before forwarding the account request to the store.
async fn get_account(
&self,
request: Request<proto::rpc::AccountRequest>,
) -> Result<Response<proto::rpc::AccountResponse>, Status> {
use proto::rpc::account_request::account_detail_request::storage_map_detail_request::{
SlotData::AllEntries as ProtoMapAllEntries, SlotData::MapKeys as ProtoMapKeys,
};

let request = request.into_inner();

debug!(target: COMPONENT, ?request);

// Validate total storage map key limit before forwarding to store
if let Some(details) = &request.details {
let total_keys: usize = details
.storage_maps
.iter()
.filter_map(|m| m.slot_data.as_ref())
.filter_map(|d| match d {
ProtoMapKeys(keys) => Some(keys.map_keys.len()),
ProtoMapAllEntries(_) => None,
})
.sum();
check::<QueryParamStorageMapKeyTotalLimit>(total_keys)?;
}

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

// -- Transaction submission --------------------------------------------------------------

/// Deserializes and rebuilds the transaction with MAST decorators stripped from output note
/// scripts, verifies the transaction proof, optionally re-executes via the validator if
/// transaction inputs are provided, then forwards the transaction to the block producer.
async fn submit_proven_transaction(
&self,
request: Request<proto::transaction::ProvenTransaction>,
Expand Down Expand Up @@ -285,18 +351,7 @@ impl api_server::Api for RpcService {
.account_update_details(tx.account_update().details().clone())
.add_input_notes(tx.input_notes().iter().cloned());

let stripped_outputs = tx.output_notes().iter().map(|note| match note {
OutputNote::Full(note) => {
let mut mast = note.script().mast().clone();
Arc::make_mut(&mut mast).strip_decorators();
let script = NoteScript::from_parts(mast, note.script().entrypoint());
let recipient =
NoteRecipient::new(note.serial_num(), script, note.storage().clone());
let new_note = Note::new(note.assets().clone(), note.metadata().clone(), recipient);
OutputNote::Full(new_note)
},
other => other.clone(),
});
let stripped_outputs = strip_output_note_decorators(tx.output_notes().iter());
builder = builder.add_output_notes(stripped_outputs);
let rebuilt_tx = builder.build().map_err(|e| Status::invalid_argument(e.to_string()))?;
let mut request = request;
Expand Down Expand Up @@ -330,6 +385,8 @@ impl api_server::Api for RpcService {
block_producer.clone().submit_proven_transaction(request).await
}

/// Deserializes the batch, strips MAST decorators from full output note scripts, rebuilds
/// the batch, then forwards it to the block producer.
async fn submit_proven_batch(
&self,
request: tonic::Request<proto::transaction::ProvenTransactionBatch>,
Expand All @@ -344,23 +401,8 @@ impl api_server::Api for RpcService {
.map_err(|err| Status::invalid_argument(err.as_report_context("invalid batch")))?;

// Build a new batch with output notes' decorators removed
let stripped_outputs: Vec<OutputNote> = batch
.output_notes()
.iter()
.map(|note| match note {
OutputNote::Full(note) => {
let mut mast = note.script().mast().clone();
Arc::make_mut(&mut mast).strip_decorators();
let script = NoteScript::from_parts(mast, note.script().entrypoint());
let recipient =
NoteRecipient::new(note.serial_num(), script, note.storage().clone());
let new_note =
Note::new(note.assets().clone(), note.metadata().clone(), recipient);
OutputNote::Full(new_note)
},
other => other.clone(),
})
.collect();
let stripped_outputs: Vec<OutputNote> =
strip_output_note_decorators(batch.output_notes().iter()).collect();

let rebuilt_batch = ProvenBatch::new(
batch.id(),
Expand Down Expand Up @@ -388,44 +430,17 @@ impl api_server::Api for RpcService {
block_producer.clone().submit_proven_batch(request).await
}

async fn get_block_by_number(
&self,
request: Request<proto::blockchain::BlockNumber>,
) -> Result<Response<proto::blockchain::MaybeBlock>, Status> {
let request = request.into_inner();

debug!(target: COMPONENT, ?request);
// -- Status & utility endpoints ----------------------------------------------------------

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

async fn get_account(
async fn sync_transactions(
&self,
request: Request<proto::rpc::AccountRequest>,
) -> Result<Response<proto::rpc::AccountResponse>, Status> {
use proto::rpc::account_request::account_detail_request::storage_map_detail_request::{
SlotData::AllEntries as ProtoMapAllEntries, SlotData::MapKeys as ProtoMapKeys,
};

let request = request.into_inner();

debug!(target: COMPONENT, ?request);
request: Request<proto::rpc::SyncTransactionsRequest>,
) -> Result<Response<proto::rpc::SyncTransactionsResponse>, Status> {
debug!(target: COMPONENT, request = ?request);

// Validate total storage map key limit before forwarding to store
if let Some(details) = &request.details {
let total_keys: usize = details
.storage_maps
.iter()
.filter_map(|m| m.slot_data.as_ref())
.filter_map(|d| match d {
ProtoMapKeys(keys) => Some(keys.map_keys.len()),
ProtoMapAllEntries(_) => None,
})
.sum();
check::<QueryParamStorageMapKeyTotalLimit>(total_keys)?;
}
check::<QueryParamAccountIdLimit>(request.get_ref().account_ids.len())?;

self.store.clone().get_account(request).await
self.store.clone().sync_transactions(request).await
}

async fn status(
Expand Down Expand Up @@ -464,26 +479,6 @@ impl api_server::Api for RpcService {
}))
}

async fn get_note_script_by_root(
&self,
request: Request<proto::note::NoteRoot>,
) -> Result<Response<proto::rpc::MaybeNoteScript>, Status> {
debug!(target: COMPONENT, request = ?request);

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

async fn sync_transactions(
&self,
request: Request<proto::rpc::SyncTransactionsRequest>,
) -> Result<Response<proto::rpc::SyncTransactionsResponse>, Status> {
debug!(target: COMPONENT, request = ?request);

check::<QueryParamAccountIdLimit>(request.get_ref().account_ids.len())?;

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

async fn get_limits(
&self,
request: Request<()>,
Expand All @@ -494,6 +489,29 @@ impl api_server::Api for RpcService {
}
}

// HELPERS
// ================================================================================================

/// Strips decorators from full output notes' scripts.
///
/// This removes MAST decorators from note scripts before forwarding to the block producer,
/// as decorators are not needed for transaction processing.
fn strip_output_note_decorators<'a>(
notes: impl Iterator<Item = &'a OutputNote> + 'a,
) -> impl Iterator<Item = OutputNote> + 'a {
notes.map(|note| match note {
OutputNote::Full(note) => {
let mut mast = note.script().mast().clone();
Arc::make_mut(&mut mast).strip_decorators();
let script = NoteScript::from_parts(mast, note.script().entrypoint());
let recipient = NoteRecipient::new(note.serial_num(), script, note.storage().clone());
let new_note = Note::new(note.assets().clone(), note.metadata().clone(), recipient);
OutputNote::Full(new_note)
},
other => other.clone(),
})
}

// LIMIT HELPERS
// ================================================================================================

Expand Down