miden-node-rpc 0.17.0-rc.3

Miden node's front-end RPC server
Documentation
use miden_node_proto::{DecodeMessage, Verify, generated as proto};
use miden_node_tracing::{debug, miden_instrument, miden_span_record};
use miden_node_utils::limiter::QueryParamNullifierPrefixLimit;

use super::error_codes::SyncNullifiersErrorCode;
use super::{RpcService, check, database_error_to_status, invalid_block_range_to_status};
use crate::{COMPONENT, LOG_TARGET};

#[tonic::async_trait]
impl proto::server::rpc_api::SyncNullifiers for RpcService {
    type Input = proto::rpc::DecodedSyncNullifiersRequest;
    type Output = proto::rpc::SyncNullifiersResponse;

    fn decode(request: proto::rpc::SyncNullifiersRequest) -> tonic::Result<Self::Input> {
        request
            .decode_fields()
            .map_err(|err| SyncNullifiersErrorCode::DeserializationFailed.invalid_argument(err))
    }

    fn encode(output: Self::Output) -> tonic::Result<proto::rpc::SyncNullifiersResponse> {
        Ok(output)
    }

    #[miden_instrument(
        target = COMPONENT,
        name = "sync_nullifiers",
        err,
    )]
    async fn handle(
        &self,
        request: Self::Input,
        _metadata: &tonic::metadata::MetadataMap,
        _extensions: &tonic::codegen::http::Extensions,
    ) -> tonic::Result<Self::Output> {
        let range = request.block_range;
        let nullifiers = request.nullifiers.into_inner();

        miden_span_record!(
            block_range.from = range.block_from,
            block_range.to = range.block_to,
            prefix_len = request.prefix_len,
            prefixes = nullifiers.as_slice() #[nonstandard]
        );

        debug!(
            target: LOG_TARGET,
            "Syncing nullifiers",
            block_range.from = range.block_from,
            block_range.to = range.block_to,
            prefix_len = request.prefix_len,
            prefixes = nullifiers.as_slice() #[nonstandard]
        );

        check::<QueryParamNullifierPrefixLimit>(nullifiers.len())?;

        if request.prefix_len != 16 {
            return Err(SyncNullifiersErrorCode::InvalidPrefixLength.invalid_argument(format!(
                "unsupported prefix length: {} (only 16-bit prefixes are supported)",
                request.prefix_len
            )));
        }

        // Every prefix must fit in the requested 16-bit prefix length. The store narrows prefixes
        // with `prefix as u16`, which would otherwise silently truncate an out-of-range value (e.g.
        // 65536 -> 0) and query a different prefix than the client requested.
        if let Some(&prefix) = nullifiers.iter().find(|&&prefix| prefix > u32::from(u16::MAX)) {
            return Err(SyncNullifiersErrorCode::DeserializationFailed.invalid_argument(format!(
                "nullifier prefix {prefix} does not fit in the requested prefix length of 16 bits"
            )));
        }

        let block_range = range.verify().map_err(invalid_block_range_to_status)?;
        let (chain_tip, (nullifiers, block_num)) = self
            .state
            .with_view(async |view| {
                view.sync_nullifiers(request.prefix_len, nullifiers, block_range)
                    .await
                    .map(|nullifiers| (view.tip(), nullifiers))
                    .map_err(|err| database_error_to_status(&err))
            })
            .await?;
        let nullifiers = nullifiers
            .into_iter()
            .map(|nullifier_info| proto::rpc::sync_nullifiers_response::NullifierUpdate {
                nullifier: Some(nullifier_info.nullifier.as_word().into()),
                block_num: nullifier_info.block_num.as_u32(),
            })
            .collect();

        Ok(proto::rpc::SyncNullifiersResponse {
            pagination_info: Some(proto::rpc::PaginationInfo {
                chain_tip: chain_tip.as_u32(),
                block_num: block_num.as_u32(),
            }),
            nullifiers,
        })
    }
}