Skip to content
Merged
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
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