From 0efab791d92061535454296b8a39db0ab7178f74 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 6 Mar 2026 08:21:20 +1300 Subject: [PATCH 01/26] Initial re impl --- crates/block-producer/src/store/mod.rs | 26 ++++-- crates/ntx-builder/src/store.rs | 68 +++++++++------ crates/proto/src/domain/account.rs | 113 +++++++++++++++++-------- crates/proto/src/domain/batch.rs | 36 ++++++-- crates/proto/src/domain/block.rs | 81 +++++++++++++----- crates/proto/src/domain/digest.rs | 49 ++++++++--- crates/proto/src/domain/mempool.rs | 54 ++++++++++-- crates/proto/src/domain/merkle.rs | 64 +++++++++++--- crates/proto/src/domain/note.rs | 76 ++++++++++++----- crates/proto/src/domain/nullifier.rs | 31 ++++++- crates/proto/src/domain/transaction.rs | 31 +++++-- crates/proto/src/errors/mod.rs | 110 ++++++++++++++---------- crates/rpc/src/server/api.rs | 25 +++--- crates/store/src/server/api.rs | 39 +++++---- crates/store/src/server/ntx_builder.rs | 5 +- crates/store/src/server/rpc_api.rs | 8 +- 16 files changed, 576 insertions(+), 240 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index fb20bc160e..14c59f13e4 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -94,7 +94,7 @@ impl TryFrom for TransactionInputs { let found_unauthenticated_notes = response .found_unauthenticated_notes .into_iter() - .map(Word::try_from) + .map(|d| Word::try_from(d).map_err(ConversionError::from)) .collect::>()?; let current_block_height = response.block_height.into(); @@ -148,11 +148,17 @@ impl StoreClient { .await? .into_inner() .block_header - .ok_or(miden_node_proto::generated::blockchain::BlockHeader::missing_field( - "block_header", - ))?; - - BlockHeader::try_from(response).map_err(Into::into) + .ok_or_else(|| { + StoreError::DeserializationError( + miden_node_proto::generated::blockchain::BlockHeader::missing_field( + "block_header", + ) + .into(), + ) + })?; + + BlockHeader::try_from(response) + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) } #[instrument(target = COMPONENT, name = "store.client.get_tx_inputs", skip_all, err)] @@ -219,7 +225,9 @@ impl StoreClient { let store_response = self.client.clone().get_block_inputs(request).await?.into_inner(); - store_response.try_into().map_err(Into::into) + store_response + .try_into() + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) } #[instrument(target = COMPONENT, name = "store.client.get_batch_inputs", skip_all, err)] @@ -235,7 +243,9 @@ impl StoreClient { let store_response = self.client.clone().get_batch_inputs(request).await?.into_inner(); - store_response.try_into().map_err(Into::into) + store_response + .try_into() + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) } #[instrument(target = COMPONENT, name = "store.client.apply_block", skip_all, err)] diff --git a/crates/ntx-builder/src/store.rs b/crates/ntx-builder/src/store.rs index 1f8c7b5f72..0556c887b0 100644 --- a/crates/ntx-builder/src/store.rs +++ b/crates/ntx-builder/src/store.rs @@ -4,7 +4,12 @@ use std::time::Duration; use miden_node_proto::clients::{Builder, StoreNtxBuilderClient}; use miden_node_proto::domain::account::{AccountDetails, AccountResponse, NetworkAccountId}; -use miden_node_proto::errors::ConversionError; +use miden_node_proto::errors::{ + AccountConversionError, + ConversionError, + DigestConversionError, + ProtoConversionError, +}; use miden_node_proto::generated::rpc::BlockRange; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -101,9 +106,13 @@ impl StoreClient { match response.current_block_header { // There are new blocks compared to the builder's latest state Some(block) => { - let peaks = try_convert(response.current_peaks).collect::>()?; - let header = - BlockHeader::try_from(block).map_err(StoreError::DeserializationError)?; + let peaks: Vec = try_convert(response.current_peaks) + .collect::>() + .map_err(|e: DigestConversionError| { + StoreError::DeserializationError(ConversionError::from(e)) + })?; + let header = BlockHeader::try_from(block) + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; let peaks = MmrPeaks::new(Forest::new(header.block_num().as_usize()), peaks) .map_err(|_| { @@ -140,9 +149,9 @@ impl StoreClient { // which implies details being public, so OK to error otherwise let account = match store_response.map(|acc| acc.details) { Some(Some(details)) => Some(Account::read_from_bytes(&details).map_err(|err| { - StoreError::DeserializationError(ConversionError::deserialization_error( - "account", err, - )) + StoreError::DeserializationError( + ProtoConversionError::deserialization_error("account", err).into(), + ) })?), _ => None, }; @@ -178,14 +187,15 @@ impl StoreClient { let proto_response = self.inner.clone().get_account(proto_request).await?.into_inner(); // Convert proto response to domain type. - let account_response = - AccountResponse::try_from(proto_response).map_err(StoreError::DeserializationError)?; + let account_response = AccountResponse::try_from(proto_response) + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; // Build partial account. let account_details = account_response .details .ok_or(StoreError::MissingDetails("account details".into()))?; - let partial_account = build_minimal_foreign_account(&account_details)?; + let partial_account = build_minimal_foreign_account(&account_details) + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; Ok(AccountInputs::new(partial_account, account_response.witness)) } @@ -216,7 +226,10 @@ impl StoreClient { all_notes.reserve(resp.notes.len()); for note in resp.notes { - all_notes.push(AccountTargetNetworkNote::try_from(note)?); + all_notes.push( + AccountTargetNetworkNote::try_from(note) + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?, + ); } match resp.next_token { @@ -317,10 +330,9 @@ impl StoreClient { .into_iter() .map(|account_id| { let account_id = AccountId::read_from_bytes(&account_id.id).map_err(|err| { - StoreError::DeserializationError(ConversionError::deserialization_error( - "account_id", - err, - )) + StoreError::DeserializationError( + ProtoConversionError::deserialization_error("account_id", err).into(), + ) })?; NetworkAccountId::try_from(account_id).map_err(|_| { StoreError::MalformedResponse( @@ -330,12 +342,12 @@ impl StoreClient { }) .collect::, StoreError>>()?; - let pagination_info = response.pagination_info.ok_or( - ConversionError::MissingFieldInProtobufRepresentation { + let pagination_info = response.pagination_info.ok_or(ConversionError::from( + ProtoConversionError::MissingField { entity: "NetworkAccountIdList", field_name: "pagination_info", }, - )?; + ))?; Ok((accounts, pagination_info)) } @@ -373,7 +385,7 @@ impl StoreClient { script .map(NoteScript::try_from) .transpose() - .map_err(StoreError::DeserializationError) + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) } #[instrument(target = COMPONENT, name = "store.client.get_vault_asset_witnesses", skip_all, err)] @@ -406,10 +418,12 @@ impl StoreClient { let smt_opening = asset_witness.proof.ok_or_else(|| { StoreError::MalformedResponse("missing proof in vault asset witness".to_string()) })?; - let proof: SmtProof = - smt_opening.try_into().map_err(StoreError::DeserializationError)?; - let witness = AssetWitness::new(proof) - .map_err(|err| StoreError::DeserializationError(ConversionError::from(err)))?; + let proof: SmtProof = smt_opening + .try_into() + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; + let witness = AssetWitness::new(proof).map_err(|err| { + StoreError::DeserializationError(AccountConversionError::from(err).into()) + })?; asset_witnesses.push(witness); } @@ -445,7 +459,9 @@ impl StoreClient { StoreError::MalformedResponse("missing proof in storage map witness".to_string()) })?; - let proof: SmtProof = smt_opening.try_into().map_err(StoreError::DeserializationError)?; + let proof: SmtProof = smt_opening + .try_into() + .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; // Create the storage map witness using the proof and raw map key. let witness = StorageMapWitness::new(proof, [map_key]).map_err(|_err| { @@ -480,12 +496,12 @@ pub enum StoreError { /// to retrieve foreign account data during transaction execution. pub fn build_minimal_foreign_account( account_details: &AccountDetails, -) -> Result { +) -> Result { // Derive account code. let account_code_bytes = account_details .account_code .as_ref() - .ok_or(ConversionError::AccountCodeMissing)?; + .ok_or(AccountConversionError::AccountCodeMissing)?; let account_code = AccountCode::from_bytes(account_code_bytes)?; // Derive partial storage. Storage maps are not required for foreign accounts. diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index aeec888328..99b48ffb41 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -19,15 +19,49 @@ use miden_protocol::block::BlockNumber; use miden_protocol::block::account_tree::AccountWitness; use miden_protocol::crypto::merkle::SparseMerklePath; use miden_protocol::crypto::merkle::smt::SmtProof; +use miden_protocol::errors::{AccountError, AssetError, StorageSlotNameError}; use miden_protocol::note::NoteAttachment; use miden_protocol::utils::{Deserializable, DeserializationError, Serializable}; use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError}; use thiserror::Error; use super::try_convert; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::domain::digest::DigestConversionError; +use crate::domain::merkle::MerkleConversionError; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::generated::{self as proto}; +// ACCOUNT CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum AccountConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Digest(#[from] DigestConversionError), + #[error(transparent)] + Merkle(#[from] MerkleConversionError), + #[error("account error")] + AccountError(#[from] AccountError), + #[error("asset error")] + AssetError(#[from] AssetError), + #[error("storage slot name error")] + StorageSlotNameError(#[from] StorageSlotNameError), + #[error("enum variant discriminant out of range")] + EnumDiscriminantOutOfRange, + #[error("value is not in the range 0..MODULUS")] + NotAValidFelt, + #[error("account code missing")] + AccountCodeMissing, +} + +impl From for tonic::Status { + fn from(value: AccountConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + #[cfg(test)] mod tests; @@ -54,10 +88,11 @@ impl Debug for proto::account::AccountId { // ------------------------------------------------------------------------------------------------ impl TryFrom for AccountId { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(account_id: proto::account::AccountId) -> Result { - AccountId::read_from_bytes(&account_id.id).map_err(|_| ConversionError::NotAValidFelt) + AccountId::read_from_bytes(&account_id.id) + .map_err(|_| AccountConversionError::NotAValidFelt) } } @@ -115,7 +150,7 @@ impl From<&AccountInfo> for proto::account::AccountDetails { //================================================================================================ impl TryFrom for AccountStorageHeader { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::account::AccountStorageHeader) -> Result { let proto::account::AccountStorageHeader { slots } = value; @@ -126,10 +161,10 @@ impl TryFrom for AccountStorageHeader { let slot_name = StorageSlotName::new(slot.slot_name)?; let slot_type = storage_slot_type_from_raw(slot.slot_type)?; let commitment = - slot.commitment.ok_or(ConversionError::NotAValidFelt)?.try_into()?; + slot.commitment.ok_or(AccountConversionError::NotAValidFelt)?.try_into()?; Ok(StorageSlotHeader::new(slot_name, slot_type, commitment)) }) - .collect::, ConversionError>>()?; + .collect::, AccountConversionError>>()?; Ok(AccountStorageHeader::new(slot_headers)?) } @@ -147,7 +182,7 @@ pub struct AccountRequest { } impl TryFrom for AccountRequest { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::rpc::AccountRequest) -> Result { let proto::rpc::AccountRequest { account_id, block_num, details } = value; @@ -171,7 +206,7 @@ pub struct AccountDetailRequest { } impl TryFrom for AccountDetailRequest { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from( value: proto::rpc::account_request::AccountDetailRequest, @@ -203,7 +238,7 @@ pub struct StorageMapRequest { impl TryFrom for StorageMapRequest { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from( value: proto::rpc::account_request::account_detail_request::StorageMapDetailRequest, @@ -232,7 +267,7 @@ impl proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData, > for SlotData { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from( value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData, @@ -242,7 +277,7 @@ impl Ok(match value { ProtoSlotData::AllEntries(true) => SlotData::All, ProtoSlotData::AllEntries(false) => { - return Err(ConversionError::EnumDiscriminantOutOfRange); + return Err(AccountConversionError::EnumDiscriminantOutOfRange); }, ProtoSlotData::MapKeys(keys) => { let keys = try_convert(keys.map_keys).collect::, _>>()?; @@ -256,7 +291,7 @@ impl //================================================================================================ impl TryFrom for AccountHeader { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::account::AccountHeader) -> Result { let proto::account::AccountHeader { @@ -279,7 +314,7 @@ impl TryFrom for AccountHeader { let code_commitment = code_commitment .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))? .try_into()?; - let nonce = nonce.try_into().map_err(|_e| ConversionError::NotAValidFelt)?; + let nonce = nonce.try_into().map_err(|_e| AccountConversionError::NotAValidFelt)?; Ok(AccountHeader::new( account_id, @@ -365,7 +400,7 @@ impl AccountVaultDetails { } impl TryFrom for AccountVaultDetails { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::rpc::AccountVaultDetails) -> Result { let proto::rpc::AccountVaultDetails { too_many_assets, assets } = value; @@ -373,14 +408,15 @@ impl TryFrom for AccountVaultDetails { if too_many_assets { Ok(Self::LimitExceeded) } else { - let parsed_assets = - Result::, ConversionError>::from_iter(assets.into_iter().map(|asset| { + let parsed_assets = Result::, AccountConversionError>::from_iter( + assets.into_iter().map(|asset| { let asset = asset .asset .ok_or(proto::primitives::Asset::missing_field(stringify!(asset)))?; let asset = Word::try_from(asset)?; - Asset::try_from(asset).map_err(ConversionError::AssetError) - }))?; + Asset::try_from(asset).map_err(AccountConversionError::AssetError) + }), + )?; Ok(Self::Assets(parsed_assets)) } } @@ -516,7 +552,7 @@ impl AccountStorageMapDetails { impl TryFrom for AccountStorageMapDetails { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from( value: proto::rpc::account_storage_details::AccountStorageMapDetails, @@ -545,7 +581,8 @@ impl TryFrom return Err( proto::rpc::account_storage_details::AccountStorageMapDetails::missing_field( stringify!(entries), - ), + ) + .into(), ); }, Some(ProtoEntries::AllEntries(AllMapEntries { entries })) => { @@ -563,7 +600,7 @@ impl TryFrom .try_into()?; Ok((key, value)) }) - .collect::, ConversionError>>()?; + .collect::, AccountConversionError>>()?; StorageMapEntries::AllEntries(entries) }, Some(ProtoEntries::EntriesWithProofs(MapEntriesWithProofs { entries })) => { @@ -573,9 +610,9 @@ impl TryFrom let smt_opening = entry.proof.ok_or( StorageMapEntryWithProof::missing_field(stringify!(proof)), )?; - SmtProof::try_from(smt_opening) + SmtProof::try_from(smt_opening).map_err(AccountConversionError::from) }) - .collect::, ConversionError>>()?; + .collect::, AccountConversionError>>()?; StorageMapEntries::EntriesWithProofs(proofs) }, } @@ -668,7 +705,7 @@ impl AccountStorageDetails { } impl TryFrom for AccountStorageDetails { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::rpc::AccountStorageDetails) -> Result { let proto::rpc::AccountStorageDetails { header, map_details } = value; @@ -694,11 +731,13 @@ impl From for proto::rpc::AccountStorageDetails { } } -const fn storage_slot_type_from_raw(slot_type: u32) -> Result { +const fn storage_slot_type_from_raw( + slot_type: u32, +) -> Result { Ok(match slot_type { 0 => StorageSlotType::Value, 1 => StorageSlotType::Map, - _ => return Err(ConversionError::EnumDiscriminantOutOfRange), + _ => return Err(AccountConversionError::EnumDiscriminantOutOfRange), }) } @@ -720,7 +759,7 @@ pub struct AccountResponse { } impl TryFrom for AccountResponse { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::rpc::AccountResponse) -> Result { let proto::rpc::AccountResponse { block_num, witness, details } = value; @@ -781,7 +820,7 @@ impl AccountDetails { } impl TryFrom for AccountDetails { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result { let proto::rpc::account_response::AccountDetails { @@ -844,7 +883,7 @@ impl From for proto::rpc::account_response::AccountDetails { // ================================================================================================ impl TryFrom for AccountWitness { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(account_witness: proto::account::AccountWitness) -> Result { let witness_id = account_witness @@ -860,12 +899,12 @@ impl TryFrom for AccountWitness { .ok_or(proto::account::AccountWitness::missing_field(stringify!(path)))? .try_into()?; - AccountWitness::new(witness_id, commitment, path).map_err(|err| { - ConversionError::deserialization_error( + Ok(AccountWitness::new(witness_id, commitment, path).map_err(|err| { + ProtoConversionError::deserialization_error( "AccountWitness", DeserializationError::InvalidValue(err.to_string()), ) - }) + })?) } } @@ -890,7 +929,7 @@ pub struct AccountWitnessRecord { } impl TryFrom for AccountWitnessRecord { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from( account_witness_record: proto::account::AccountWitness, @@ -911,7 +950,7 @@ impl TryFrom for AccountWitnessRecord { .try_into()?; let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| { - ConversionError::deserialization_error( + ProtoConversionError::deserialization_error( "AccountWitness", DeserializationError::InvalidValue(err.to_string()), ) @@ -961,7 +1000,7 @@ impl Display for AccountState { } impl TryFrom for AccountState { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from( from: proto::store::transaction_inputs::AccountTransactionInputRecord, @@ -1005,13 +1044,13 @@ impl From for proto::store::transaction_inputs::AccountTransaction // ================================================================================================ impl TryFrom for Asset { - type Error = ConversionError; + type Error = AccountConversionError; fn try_from(value: proto::primitives::Asset) -> Result { let inner = value.asset.ok_or(proto::primitives::Asset::missing_field("asset"))?; let word = Word::try_from(inner)?; - Asset::try_from(word).map_err(ConversionError::AssetError) + Asset::try_from(word).map_err(AccountConversionError::AssetError) } } diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index 1cccf6ab8b..0e26b885f1 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -4,10 +4,32 @@ use miden_protocol::block::BlockHeader; use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; +use thiserror::Error; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::domain::block::BlockConversionError; +use crate::domain::note::NoteConversionError; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::generated as proto; +// BATCH CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum BatchConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Block(#[from] BlockConversionError), + #[error(transparent)] + Note(#[from] NoteConversionError), +} + +impl From for tonic::Status { + fn from(value: BatchConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + /// Data required for a transaction batch. #[derive(Clone, Debug)] pub struct BatchInputs { @@ -27,9 +49,9 @@ impl From for proto::store::BatchInputs { } impl TryFrom for BatchInputs { - type Error = ConversionError; + type Error = BatchConversionError; - fn try_from(response: proto::store::BatchInputs) -> Result { + fn try_from(response: proto::store::BatchInputs) -> Result { let result = Self { batch_reference_block_header: response .batch_reference_block_header @@ -38,11 +60,13 @@ impl TryFrom for BatchInputs { note_proofs: response .note_proofs .iter() - .map(<(NoteId, NoteInclusionProof)>::try_from) - .collect::>()?, + .map(|p| { + <(NoteId, NoteInclusionProof)>::try_from(p).map_err(BatchConversionError::from) + }) + .collect::>()?, partial_block_chain: PartialBlockchain::read_from_bytes(&response.partial_block_chain) .map_err(|source| { - ConversionError::deserialization_error("PartialBlockchain", source) + ProtoConversionError::deserialization_error("PartialBlockchain", source) })?, }; diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 112f84e50b..80394286d8 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -12,14 +12,49 @@ use miden_protocol::block::{ SignedBlock, }; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; +use miden_protocol::errors::{AccountError, FeeError}; use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; use thiserror::Error; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::domain::account::AccountConversionError; +use crate::domain::digest::DigestConversionError; +use crate::domain::merkle::MerkleConversionError; +use crate::domain::note::NoteConversionError; +use crate::domain::nullifier::NullifierConversionError; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; +// BLOCK CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum BlockConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Digest(#[from] DigestConversionError), + #[error(transparent)] + Account(#[from] AccountConversionError), + #[error(transparent)] + Merkle(#[from] MerkleConversionError), + #[error(transparent)] + Note(#[from] NoteConversionError), + #[error(transparent)] + Nullifier(#[from] NullifierConversionError), + #[error("fee parameters error")] + FeeError(#[from] FeeError), + #[error("account error")] + AccountError(#[from] AccountError), +} + +impl From for tonic::Status { + fn from(value: BlockConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + // BLOCK NUMBER // ================================================================================================ @@ -64,7 +99,7 @@ impl From for proto::blockchain::BlockHeader { } impl TryFrom<&proto::blockchain::BlockHeader> for BlockHeader { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(value: &proto::blockchain::BlockHeader) -> Result { value.try_into() @@ -72,7 +107,7 @@ impl TryFrom<&proto::blockchain::BlockHeader> for BlockHeader { } impl TryFrom for BlockHeader { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(value: proto::blockchain::BlockHeader) -> Result { Ok(BlockHeader::new( @@ -138,7 +173,7 @@ impl From for proto::blockchain::BlockBody { } impl TryFrom<&proto::blockchain::BlockBody> for BlockBody { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(value: &proto::blockchain::BlockBody) -> Result { value.try_into() @@ -146,10 +181,10 @@ impl TryFrom<&proto::blockchain::BlockBody> for BlockBody { } impl TryFrom for BlockBody { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(value: proto::blockchain::BlockBody) -> Result { - BlockBody::read_from_bytes(&value.block_body) - .map_err(|source| ConversionError::deserialization_error("BlockBody", source)) + Ok(BlockBody::read_from_bytes(&value.block_body) + .map_err(|source| ProtoConversionError::deserialization_error("BlockBody", source))?) } } @@ -173,7 +208,7 @@ impl From for proto::blockchain::SignedBlock { } impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(value: &proto::blockchain::SignedBlock) -> Result { value.try_into() @@ -181,7 +216,7 @@ impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { } impl TryFrom for SignedBlock { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(value: proto::blockchain::SignedBlock) -> Result { let header = value .header @@ -236,7 +271,7 @@ impl From for proto::store::BlockInputs { } impl TryFrom for BlockInputs { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(response: proto::store::BlockInputs) -> Result { let latest_block_header: BlockHeader = response @@ -251,7 +286,7 @@ impl TryFrom for BlockInputs { let witness_record: AccountWitnessRecord = entry.try_into()?; Ok((witness_record.account_id, witness_record.witness)) }) - .collect::, ConversionError>>()?; + .collect::, BlockConversionError>>()?; let nullifier_witnesses = response .nullifier_witnesses @@ -260,17 +295,19 @@ impl TryFrom for BlockInputs { let witness: NullifierWitnessRecord = entry.try_into()?; Ok((witness.nullifier, NullifierWitness::new(witness.proof))) }) - .collect::, ConversionError>>()?; + .collect::, BlockConversionError>>()?; let unauthenticated_note_proofs = response .unauthenticated_note_proofs .iter() - .map(<(NoteId, NoteInclusionProof)>::try_from) - .collect::>()?; + .map(|p| { + <(NoteId, NoteInclusionProof)>::try_from(p).map_err(BlockConversionError::from) + }) + .collect::>()?; let partial_block_chain = PartialBlockchain::read_from_bytes(&response.partial_block_chain) .map_err(|source| { - ConversionError::deserialization_error("PartialBlockchain", source) + ProtoConversionError::deserialization_error("PartialBlockchain", source) })?; Ok(BlockInputs::new( @@ -287,10 +324,10 @@ impl TryFrom for BlockInputs { // ================================================================================================ impl TryFrom for PublicKey { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(public_key: proto::blockchain::ValidatorPublicKey) -> Result { - PublicKey::read_from_bytes(&public_key.validator_key) - .map_err(|source| ConversionError::deserialization_error("PublicKey", source)) + Ok(PublicKey::read_from_bytes(&public_key.validator_key) + .map_err(|source| ProtoConversionError::deserialization_error("PublicKey", source))?) } } @@ -310,10 +347,10 @@ impl From<&PublicKey> for proto::blockchain::ValidatorPublicKey { // ================================================================================================ impl TryFrom for Signature { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(signature: proto::blockchain::BlockSignature) -> Result { - Signature::read_from_bytes(&signature.signature) - .map_err(|source| ConversionError::deserialization_error("Signature", source)) + Ok(Signature::read_from_bytes(&signature.signature) + .map_err(|source| ProtoConversionError::deserialization_error("Signature", source))?) } } @@ -333,7 +370,7 @@ impl From<&Signature> for proto::blockchain::BlockSignature { // ================================================================================================ impl TryFrom for FeeParameters { - type Error = ConversionError; + type Error = BlockConversionError; fn try_from(fee_params: proto::blockchain::FeeParameters) -> Result { let native_asset_id = fee_params.native_asset_id.map(AccountId::try_from).ok_or( proto::blockchain::FeeParameters::missing_field(stringify!(native_asset_id)), diff --git a/crates/proto/src/domain/digest.rs b/crates/proto/src/domain/digest.rs index 08d8c3f9a1..5a8e1bdcc0 100644 --- a/crates/proto/src/domain/digest.rs +++ b/crates/proto/src/domain/digest.rs @@ -4,10 +4,34 @@ use hex::{FromHex, ToHex}; use miden_protocol::account::StorageMapKey; use miden_protocol::note::NoteId; use miden_protocol::{Felt, StarkField, Word}; +use thiserror::Error; -use crate::errors::ConversionError; +use crate::errors::ProtoConversionError; use crate::generated as proto; +// DIGEST CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum DigestConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error("hex error")] + HexError(#[from] hex::FromHexError), + #[error("too much data, expected {expected}, got {got}")] + TooMuchData { expected: usize, got: usize }, + #[error("not enough data, expected {expected}, got {got}")] + InsufficientData { expected: usize, got: usize }, + #[error("value is not in the range 0..MODULUS")] + NotAValidFelt, +} + +impl From for tonic::Status { + fn from(value: DigestConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + // CONSTANTS // ================================================================================================ @@ -59,17 +83,18 @@ impl ToHex for proto::primitives::Digest { } impl FromHex for proto::primitives::Digest { - type Error = ConversionError; + type Error = DigestConversionError; fn from_hex>(hex: T) -> Result { let data = hex::decode(hex)?; match data.len() { - size if size < DIGEST_DATA_SIZE => { - Err(ConversionError::InsufficientData { expected: DIGEST_DATA_SIZE, got: size }) - }, + size if size < DIGEST_DATA_SIZE => Err(DigestConversionError::InsufficientData { + expected: DIGEST_DATA_SIZE, + got: size, + }), size if size > DIGEST_DATA_SIZE => { - Err(ConversionError::TooMuchData { expected: DIGEST_DATA_SIZE, got: size }) + Err(DigestConversionError::TooMuchData { expected: DIGEST_DATA_SIZE, got: size }) }, _ => { let d0 = u64::from_be_bytes(data[..8].try_into().unwrap()); @@ -171,14 +196,14 @@ impl From for [u64; 4] { } impl TryFrom for [Felt; 4] { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(value: proto::primitives::Digest) -> Result { if [value.d0, value.d1, value.d2, value.d3] .iter() .any(|v| *v >= ::MODULUS) { - return Err(ConversionError::NotAValidFelt); + return Err(DigestConversionError::NotAValidFelt); } Ok([ @@ -191,7 +216,7 @@ impl TryFrom for [Felt; 4] { } impl TryFrom for Word { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(value: proto::primitives::Digest) -> Result { Ok(Self::new(value.try_into()?)) @@ -199,7 +224,7 @@ impl TryFrom for Word { } impl TryFrom for StorageMapKey { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(value: proto::primitives::Digest) -> Result { Ok(StorageMapKey::new(value.try_into()?)) @@ -207,7 +232,7 @@ impl TryFrom for StorageMapKey { } impl TryFrom<&proto::primitives::Digest> for [Felt; 4] { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(value: &proto::primitives::Digest) -> Result { (*value).try_into() @@ -215,7 +240,7 @@ impl TryFrom<&proto::primitives::Digest> for [Felt; 4] { } impl TryFrom<&proto::primitives::Digest> for Word { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(value: &proto::primitives::Digest) -> Result { (*value).try_into() diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index c9bf76bfc9..c08720f818 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -6,10 +6,38 @@ use miden_protocol::note::Nullifier; use miden_protocol::transaction::TransactionId; use miden_protocol::utils::{Deserializable, Serializable}; use miden_standards::note::AccountTargetNetworkNote; +use thiserror::Error; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::domain::block::BlockConversionError; +use crate::domain::digest::DigestConversionError; +use crate::domain::note::NoteConversionError; +use crate::domain::transaction::TransactionConversionError; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::generated as proto; +// MEMPOOL CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum MempoolConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Block(#[from] BlockConversionError), + #[error(transparent)] + Transaction(#[from] TransactionConversionError), + #[error(transparent)] + Note(#[from] NoteConversionError), + #[error(transparent)] + Digest(#[from] DigestConversionError), +} + +impl From for tonic::Status { + fn from(value: MempoolConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + #[derive(Debug, Clone)] pub enum MempoolEvent { TransactionAdded { @@ -79,7 +107,7 @@ impl From for proto::block_producer::MempoolEvent { } impl TryFrom for MempoolEvent { - type Error = ConversionError; + type Error = MempoolConversionError; fn try_from(event: proto::block_producer::MempoolEvent) -> Result { let event = @@ -92,20 +120,28 @@ impl TryFrom for MempoolEvent { .ok_or(proto::block_producer::mempool_event::TransactionAdded::missing_field( "id", ))? - .try_into()?; - let nullifiers = - tx.nullifiers.into_iter().map(TryInto::try_into).collect::>()?; + .try_into() + .map_err(MempoolConversionError::from)?; + let nullifiers = tx + .nullifiers + .into_iter() + .map(|n| Nullifier::try_from(n).map_err(MempoolConversionError::from)) + .collect::>()?; let network_notes = tx .network_notes .into_iter() - .map(TryInto::try_into) + .map(|n| { + AccountTargetNetworkNote::try_from(n).map_err(MempoolConversionError::from) + }) .collect::>()?; let account_delta = tx .network_account_delta .as_deref() .map(AccountUpdateDetails::read_from_bytes) .transpose() - .map_err(|err| ConversionError::deserialization_error("account_delta", err))?; + .map_err(|err| { + ProtoConversionError::deserialization_error("account_delta", err) + })?; Ok(Self::TransactionAdded { id, @@ -125,7 +161,7 @@ impl TryFrom for MempoolEvent { let txs = block_committed .transactions .into_iter() - .map(TransactionId::try_from) + .map(|t| TransactionId::try_from(t).map_err(MempoolConversionError::from)) .collect::>()?; Ok(Self::BlockCommitted { header, txs }) @@ -134,7 +170,7 @@ impl TryFrom for MempoolEvent { let txs = txs .reverted .into_iter() - .map(TransactionId::try_from) + .map(|t| TransactionId::try_from(t).map_err(MempoolConversionError::from)) .collect::>()?; Ok(Self::TransactionsReverted(txs)) diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index ed14d523ba..a3219c7a71 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -1,12 +1,43 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::mmr::{Forest, MmrDelta}; -use miden_protocol::crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; +use miden_protocol::crypto::merkle::smt::{ + LeafIndex, + SmtLeaf, + SmtLeafError, + SmtProof, + SmtProofError, +}; use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath}; +use thiserror::Error; +use crate::domain::digest::DigestConversionError; use crate::domain::{convert, try_convert}; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::generated as proto; +// MERKLE CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum MerkleConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Digest(#[from] DigestConversionError), + #[error("merkle error")] + MerkleError(#[from] miden_protocol::crypto::merkle::MerkleError), + #[error("SMT leaf error")] + SmtLeafError(#[from] SmtLeafError), + #[error("SMT proof error")] + SmtProofError(#[from] SmtProofError), +} + +impl From for tonic::Status { + fn from(value: MerkleConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + // MERKLE PATH // ================================================================================================ @@ -24,15 +55,19 @@ impl From for proto::primitives::MerklePath { } impl TryFrom<&proto::primitives::MerklePath> for MerklePath { - type Error = ConversionError; + type Error = MerkleConversionError; fn try_from(merkle_path: &proto::primitives::MerklePath) -> Result { - merkle_path.siblings.iter().map(Word::try_from).collect() + merkle_path + .siblings + .iter() + .map(|s| Word::try_from(s).map_err(MerkleConversionError::from)) + .collect() } } impl TryFrom for MerklePath { - type Error = ConversionError; + type Error = MerkleConversionError; fn try_from(merkle_path: proto::primitives::MerklePath) -> Result { (&merkle_path).try_into() @@ -53,7 +88,7 @@ impl From for proto::primitives::SparseMerklePath { } impl TryFrom for SparseMerklePath { - type Error = ConversionError; + type Error = MerkleConversionError; fn try_from(merkle_path: proto::primitives::SparseMerklePath) -> Result { Ok(SparseMerklePath::from_parts( @@ -61,7 +96,7 @@ impl TryFrom for SparseMerklePath { merkle_path .siblings .into_iter() - .map(Word::try_from) + .map(|s| Word::try_from(s).map_err(MerkleConversionError::from)) .collect::, _>>()?, )?) } @@ -81,11 +116,14 @@ impl From for proto::primitives::MmrDelta { } impl TryFrom for MmrDelta { - type Error = ConversionError; + type Error = MerkleConversionError; fn try_from(value: proto::primitives::MmrDelta) -> Result { - let data: Result, ConversionError> = - value.data.into_iter().map(Word::try_from).collect(); + let data: Result, MerkleConversionError> = value + .data + .into_iter() + .map(|d| Word::try_from(d).map_err(MerkleConversionError::from)) + .collect(); Ok(MmrDelta { forest: Forest::new(value.forest as usize), @@ -101,7 +139,7 @@ impl TryFrom for MmrDelta { // ------------------------------------------------------------------------------------------------ impl TryFrom for SmtLeaf { - type Error = ConversionError; + type Error = MerkleConversionError; fn try_from(value: proto::primitives::SmtLeaf) -> Result { let leaf = value.leaf.ok_or(proto::primitives::SmtLeaf::missing_field(stringify!(leaf)))?; @@ -145,7 +183,7 @@ impl From for proto::primitives::SmtLeaf { // ------------------------------------------------------------------------------------------------ impl TryFrom for (Word, Word) { - type Error = ConversionError; + type Error = MerkleConversionError; fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { let key: Word = entry @@ -174,7 +212,7 @@ impl From<(Word, Word)> for proto::primitives::SmtLeafEntry { // ------------------------------------------------------------------------------------------------ impl TryFrom for SmtProof { - type Error = ConversionError; + type Error = MerkleConversionError; fn try_from(opening: proto::primitives::SmtOpening) -> Result { let path: SparseMerklePath = opening diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index f92ac75179..ee66a59928 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -1,3 +1,4 @@ +use std::num::TryFromIntError; use std::sync::Arc; use miden_protocol::crypto::merkle::SparseMerklePath; @@ -14,11 +15,44 @@ use miden_protocol::note::{ }; use miden_protocol::utils::{Deserializable, Serializable}; use miden_protocol::{MastForest, MastNodeId, Word}; -use miden_standards::note::AccountTargetNetworkNote; +use miden_standards::note::{AccountTargetNetworkNote, NetworkAccountTargetError}; +use thiserror::Error; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::domain::account::AccountConversionError; +use crate::domain::digest::DigestConversionError; +use crate::domain::merkle::MerkleConversionError; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::generated as proto; +// NOTE CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum NoteConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Digest(#[from] DigestConversionError), + #[error(transparent)] + Merkle(#[from] MerkleConversionError), + #[error(transparent)] + Account(#[from] AccountConversionError), + #[error("note error")] + NoteError(#[from] miden_protocol::errors::NoteError), + #[error("network note error")] + NetworkNoteError(#[source] NetworkAccountTargetError), + #[error("enum variant discriminant out of range")] + EnumDiscriminantOutOfRange, + #[error("integer conversion error: {0}")] + TryFromIntError(#[from] TryFromIntError), +} + +impl From for tonic::Status { + fn from(value: NoteConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + // NOTE TYPE // ================================================================================================ @@ -32,13 +66,15 @@ impl From for proto::note::NoteType { } impl TryFrom for NoteType { - type Error = ConversionError; + type Error = NoteConversionError; fn try_from(note_type: proto::note::NoteType) -> Result { match note_type { proto::note::NoteType::Public => Ok(NoteType::Public), proto::note::NoteType::Private => Ok(NoteType::Private), - proto::note::NoteType::Unspecified => Err(ConversionError::EnumDiscriminantOutOfRange), + proto::note::NoteType::Unspecified => { + Err(NoteConversionError::EnumDiscriminantOutOfRange) + }, } } } @@ -47,15 +83,16 @@ impl TryFrom for NoteType { // ================================================================================================ impl TryFrom for NoteMetadata { - type Error = ConversionError; + type Error = NoteConversionError; fn try_from(value: proto::note::NoteMetadata) -> Result { let sender = value .sender .ok_or_else(|| proto::note::NoteMetadata::missing_field(stringify!(sender)))? - .try_into()?; + .try_into() + .map_err(NoteConversionError::from)?; let note_type = proto::note::NoteType::try_from(value.note_type) - .map_err(|_| ConversionError::EnumDiscriminantOutOfRange)? + .map_err(|_| NoteConversionError::EnumDiscriminantOutOfRange)? .try_into()?; let tag = NoteTag::new(value.tag); @@ -64,7 +101,7 @@ impl TryFrom for NoteMetadata { NoteAttachment::default() } else { NoteAttachment::read_from_bytes(&value.attachment) - .map_err(|err| ConversionError::deserialization_error("NoteAttachment", err))? + .map_err(|err| ProtoConversionError::deserialization_error("NoteAttachment", err))? }; Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment)) @@ -100,18 +137,18 @@ impl From for proto::note::NetworkNote { } impl TryFrom for AccountTargetNetworkNote { - type Error = ConversionError; + type Error = NoteConversionError; fn try_from(value: proto::note::NetworkNote) -> Result { let details = NoteDetails::read_from_bytes(&value.details) - .map_err(|err| ConversionError::deserialization_error("NoteDetails", err))?; + .map_err(|err| ProtoConversionError::deserialization_error("NoteDetails", err))?; let (assets, recipient) = details.into_parts(); let metadata: NoteMetadata = value .metadata .ok_or_else(|| proto::note::NetworkNote::missing_field(stringify!(metadata)))? .try_into()?; let note = Note::new(assets, metadata, recipient); - AccountTargetNetworkNote::new(note).map_err(ConversionError::NetworkNoteError) + AccountTargetNetworkNote::new(note).map_err(NoteConversionError::NetworkNoteError) } } @@ -133,7 +170,7 @@ impl From for proto::note::NoteId { } impl TryFrom for Word { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(note_id: proto::note::NoteId) -> Result { note_id @@ -162,7 +199,7 @@ impl From<(&NoteId, &NoteInclusionProof)> for proto::note::NoteInclusionInBlockP } impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusionProof) { - type Error = ConversionError; + type Error = NoteConversionError; fn try_from( proof: &proto::note::NoteInclusionInBlockProof, @@ -199,7 +236,7 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion } impl TryFrom for Note { - type Error = ConversionError; + type Error = NoteConversionError; fn try_from(proto_note: proto::note::Note) -> Result { let metadata: NoteMetadata = proto_note @@ -212,7 +249,7 @@ impl TryFrom for Note { .ok_or(proto::note::Note::missing_field(stringify!(details)))?; let note_details = NoteDetails::read_from_bytes(&details) - .map_err(|err| ConversionError::deserialization_error("NoteDetails", err))?; + .map_err(|err| ProtoConversionError::deserialization_error("NoteDetails", err))?; let (assets, recipient) = note_details.into_parts(); Ok(Note::new(assets, metadata, recipient)) @@ -232,15 +269,16 @@ impl From for proto::note::NoteScript { } impl TryFrom for NoteScript { - type Error = ConversionError; + type Error = NoteConversionError; fn try_from(value: proto::note::NoteScript) -> Result { let proto::note::NoteScript { entrypoint, mast } = value; let mast = MastForest::read_from_bytes(&mast) - .map_err(|err| Self::Error::deserialization_error("note_script.mast", err))?; - let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast) - .map_err(|err| Self::Error::deserialization_error("note_script.entrypoint", err))?; + .map_err(|err| ProtoConversionError::deserialization_error("note_script.mast", err))?; + let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast).map_err(|err| { + ProtoConversionError::deserialization_error("note_script.entrypoint", err) + })?; Ok(Self::from_parts(Arc::new(mast), entrypoint)) } diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index 3ccdf88bae..c259f0063d 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -1,10 +1,32 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::smt::SmtProof; use miden_protocol::note::Nullifier; +use thiserror::Error; -use crate::errors::{ConversionError, MissingFieldHelper}; +use crate::domain::digest::DigestConversionError; +use crate::domain::merkle::MerkleConversionError; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::generated as proto; +// NULLIFIER CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum NullifierConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Digest(#[from] DigestConversionError), + #[error(transparent)] + Merkle(#[from] MerkleConversionError), +} + +impl From for tonic::Status { + fn from(value: NullifierConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + // FROM NULLIFIER // ================================================================================================ @@ -24,7 +46,7 @@ impl From for proto::primitives::Digest { // ================================================================================================ impl TryFrom for Nullifier { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(value: proto::primitives::Digest) -> Result { let digest: Word = value.try_into()?; @@ -42,7 +64,7 @@ pub struct NullifierWitnessRecord { } impl TryFrom for NullifierWitnessRecord { - type Error = ConversionError; + type Error = NullifierConversionError; fn try_from( nullifier_witness_record: proto::store::block_inputs::NullifierWitness, @@ -53,7 +75,8 @@ impl TryFrom for NullifierWitnessR .ok_or(proto::store::block_inputs::NullifierWitness::missing_field(stringify!( nullifier )))? - .try_into()?, + .try_into() + .map_err(NullifierConversionError::from)?, proof: nullifier_witness_record .opening .ok_or(proto::store::block_inputs::NullifierWitness::missing_field(stringify!( diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index 4b2e29362f..b6d2f66f37 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -1,9 +1,28 @@ use miden_protocol::Word; use miden_protocol::transaction::TransactionId; +use thiserror::Error; -use crate::errors::ConversionError; +use crate::domain::digest::DigestConversionError; +use crate::errors::{MissingFieldHelper, ProtoConversionError}; use crate::generated as proto; +// TRANSACTION CONVERSION ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum TransactionConversionError { + #[error(transparent)] + Proto(#[from] ProtoConversionError), + #[error(transparent)] + Digest(#[from] DigestConversionError), +} + +impl From for tonic::Status { + fn from(value: TransactionConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + // FROM TRANSACTION ID // ================================================================================================ @@ -35,7 +54,7 @@ impl From for proto::transaction::TransactionId { // ================================================================================================ impl TryFrom for TransactionId { - type Error = ConversionError; + type Error = DigestConversionError; fn try_from(value: proto::primitives::Digest) -> Result { let digest: Word = value.try_into()?; @@ -44,15 +63,13 @@ impl TryFrom for TransactionId { } impl TryFrom for TransactionId { - type Error = ConversionError; + type Error = TransactionConversionError; fn try_from(value: proto::transaction::TransactionId) -> Result { value .id - .ok_or(ConversionError::MissingFieldInProtobufRepresentation { - entity: "TransactionId", - field_name: "id", - })? + .ok_or(proto::transaction::TransactionId::missing_field("id"))? .try_into() + .map_err(TransactionConversionError::from) } } diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 04493e6960..a68546030f 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -1,51 +1,32 @@ use std::any::type_name; -use std::num::TryFromIntError; // Re-export the GrpcError derive macro for convenience pub use miden_node_grpc_error_macro::GrpcError; -use miden_protocol::crypto::merkle::smt::{SmtLeafError, SmtProofError}; -use miden_protocol::errors::{AccountError, AssetError, FeeError, NoteError, StorageSlotNameError}; use miden_protocol::utils::DeserializationError; -use miden_standards::note::NetworkAccountTargetError; use thiserror::Error; +// Re-export per-domain conversion errors +pub use crate::domain::account::AccountConversionError; +pub use crate::domain::batch::BatchConversionError; +pub use crate::domain::block::BlockConversionError; +pub use crate::domain::digest::DigestConversionError; +pub use crate::domain::mempool::MempoolConversionError; +pub use crate::domain::merkle::MerkleConversionError; +pub use crate::domain::note::NoteConversionError; +pub use crate::domain::nullifier::NullifierConversionError; +pub use crate::domain::transaction::TransactionConversionError; + #[cfg(test)] mod test_macro; +// SHARED PROTO CONVERSION ERROR +// ================================================================================================ + +/// Shared error variants common to all protobuf conversions. #[derive(Debug, Error)] -pub enum ConversionError { - #[error("asset error")] - AssetError(#[from] AssetError), - #[error("account code missing")] - AccountCodeMissing, - #[error("account error")] - AccountError(#[from] AccountError), - #[error("fee parameters error")] - FeeError(#[from] FeeError), - #[error("hex error")] - HexError(#[from] hex::FromHexError), - #[error("note error")] - NoteError(#[from] NoteError), - #[error("network note error")] - NetworkNoteError(#[source] NetworkAccountTargetError), - #[error("SMT leaf error")] - SmtLeafError(#[from] SmtLeafError), - #[error("SMT proof error")] - SmtProofError(#[from] SmtProofError), - #[error("storage slot name error")] - StorageSlotNameError(#[from] StorageSlotNameError), - #[error("integer conversion error: {0}")] - TryFromIntError(#[from] TryFromIntError), - #[error("too much data, expected {expected}, got {got}")] - TooMuchData { expected: usize, got: usize }, - #[error("not enough data, expected {expected}, got {got}")] - InsufficientData { expected: usize, got: usize }, - #[error("value is not in the range 0..MODULUS")] - NotAValidFelt, - #[error("merkle error")] - MerkleError(#[from] miden_protocol::crypto::merkle::MerkleError), +pub enum ProtoConversionError { #[error("field `{entity}::{field_name}` is missing")] - MissingFieldInProtobufRepresentation { + MissingField { entity: &'static str, field_name: &'static str, }, @@ -54,26 +35,65 @@ pub enum ConversionError { entity: &'static str, source: DeserializationError, }, - #[error("enum variant discriminant out of range")] - EnumDiscriminantOutOfRange, } -impl ConversionError { +impl ProtoConversionError { pub fn deserialization_error(entity: &'static str, source: DeserializationError) -> Self { Self::DeserializationError { entity, source } } } +impl From for tonic::Status { + fn from(value: ProtoConversionError) -> Self { + tonic::Status::invalid_argument(value.to_string()) + } +} + pub trait MissingFieldHelper { - fn missing_field(field_name: &'static str) -> ConversionError; + fn missing_field(field_name: &'static str) -> ProtoConversionError; } impl MissingFieldHelper for T { - fn missing_field(field_name: &'static str) -> ConversionError { - ConversionError::MissingFieldInProtobufRepresentation { - entity: type_name::(), - field_name, - } + fn missing_field(field_name: &'static str) -> ProtoConversionError { + ProtoConversionError::MissingField { entity: type_name::(), field_name } + } +} + +// CONVERSION ERROR (WRAPPER) +// ================================================================================================ + +/// Union error type that wraps all per-domain conversion errors. +/// +/// This preserves backward compatibility for downstream crates that use `#[from] ConversionError`. +/// Prefer using the domain-specific error types (e.g. `DigestConversionError`, +/// `AccountConversionError`) at conversion boundaries. +#[derive(Debug, Error)] +pub enum ConversionError { + #[error(transparent)] + Digest(#[from] DigestConversionError), + #[error(transparent)] + Account(#[from] AccountConversionError), + #[error(transparent)] + Note(#[from] NoteConversionError), + #[error(transparent)] + Block(#[from] BlockConversionError), + #[error(transparent)] + Merkle(#[from] MerkleConversionError), + #[error(transparent)] + Nullifier(#[from] NullifierConversionError), + #[error(transparent)] + Transaction(#[from] TransactionConversionError), + #[error(transparent)] + Batch(#[from] BatchConversionError), + #[error(transparent)] + Mempool(#[from] MempoolConversionError), + #[error(transparent)] + Proto(#[from] ProtoConversionError), +} + +impl ConversionError { + pub fn deserialization_error(entity: &'static str, source: DeserializationError) -> Self { + Self::Proto(ProtoConversionError::deserialization_error(entity, source)) } } diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index a0ec88859a..256600b1bb 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -3,7 +3,7 @@ use std::time::Duration; use anyhow::Context; use miden_node_proto::clients::{BlockProducerClient, Builder, StoreRpcClient, ValidatorClient}; -use miden_node_proto::errors::ConversionError; +use miden_node_proto::errors::DigestConversionError; use miden_node_proto::generated::rpc::MempoolStats; use miden_node_proto::generated::rpc::api_server::{self, Api}; use miden_node_proto::generated::{self as proto}; @@ -243,12 +243,11 @@ impl api_server::Api for RpcService { // Validation checking for correct NoteId's let note_ids = request.get_ref().ids.clone(); - let _: Vec = - try_convert(note_ids) - .collect::>() - .map_err(|err: ConversionError| { - Status::invalid_argument(err.as_report_context("invalid NoteId")) - })?; + let _: Vec = try_convert(note_ids).collect::>().map_err( + |err: DigestConversionError| { + Status::invalid_argument(err.as_report_context("invalid NoteId")) + }, + )?; self.store.clone().get_notes_by_id(request).await } @@ -534,11 +533,13 @@ fn endpoint_limits(params: &[(&str, usize)]) -> proto::rpc::EndpointLimits { /// Cached RPC query parameter limits. static RPC_LIMITS: LazyLock = LazyLock::new(|| { - use QueryParamAccountIdLimit as AccountId; - use QueryParamNoteIdLimit as NoteId; - use QueryParamNoteTagLimit as NoteTag; - use QueryParamNullifierLimit as Nullifier; - use QueryParamStorageMapKeyTotalLimit as StorageMapKeyTotal; + use { + QueryParamAccountIdLimit as AccountId, + QueryParamNoteIdLimit as NoteId, + QueryParamNoteTagLimit as NoteTag, + QueryParamNullifierLimit as Nullifier, + QueryParamStorageMapKeyTotalLimit as StorageMapKeyTotal, + }; proto::rpc::RpcLimits { endpoints: std::collections::HashMap::from([ diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 56bfcafb49..b73748cb86 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -1,7 +1,12 @@ use std::collections::BTreeSet; use std::sync::Arc; -use miden_node_proto::errors::ConversionError; +use miden_node_proto::errors::{ + AccountConversionError, + ConversionError, + DigestConversionError, + ProtoConversionError, +}; use miden_node_proto::generated as proto; use miden_node_utils::ErrorReport; use miden_protocol::Word; @@ -109,8 +114,11 @@ where E: From, { block_range.ok_or_else(|| { - ConversionError::MissingFieldInProtobufRepresentation { entity, field_name: "block_range" } - .into() + ConversionError::from(ProtoConversionError::MissingField { + entity, + field_name: "block_range", + }) + .into() }) } @@ -123,12 +131,11 @@ pub fn read_root( where E: From, { - root.ok_or_else(|| ConversionError::MissingFieldInProtobufRepresentation { - entity, - field_name: "root", + root.ok_or_else(|| { + ConversionError::from(ProtoConversionError::MissingField { entity, field_name: "root" }) })? .try_into() - .map_err(Into::into) + .map_err(|e: DigestConversionError| ConversionError::from(e).into()) } /// Converts a collection of proto primitives to Words, returning a specific error type if @@ -137,13 +144,13 @@ pub fn convert_digests_to_words(digests: I) -> Result, E> where E: From, I: IntoIterator, - I::Item: TryInto, + I::Item: TryInto, { digests .into_iter() .map(TryInto::try_into) - .collect::, ConversionError>>() - .map_err(Into::into) + .collect::, DigestConversionError>>() + .map_err(|e| ConversionError::from(e).into()) } /// Reads account IDs from a request, returning a specific error type if conversion fails @@ -155,8 +162,8 @@ where .iter() .cloned() .map(AccountId::try_from) - .collect::>() - .map_err(Into::into) + .collect::>() + .map_err(|e| ConversionError::from(e).into()) } pub fn read_account_id(id: Option) -> Result @@ -164,15 +171,15 @@ where E: From, { id.ok_or_else(|| { - ConversionError::deserialization_error( + ConversionError::from(ProtoConversionError::deserialization_error( "AccountId", miden_protocol::crypto::utils::DeserializationError::InvalidValue( "Missing account ID".to_string(), ), - ) + )) })? .try_into() - .map_err(Into::into) + .map_err(|e: AccountConversionError| ConversionError::from(e).into()) } #[instrument( @@ -189,7 +196,7 @@ where nullifiers .iter() .copied() - .map(TryInto::try_into) + .map(|d| Nullifier::try_from(d).map_err(ConversionError::from)) .collect::>() .map_err(Into::into) } diff --git a/crates/store/src/server/ntx_builder.rs b/crates/store/src/server/ntx_builder.rs index f6e8d4a7a5..3508b893ad 100644 --- a/crates/store/src/server/ntx_builder.rs +++ b/crates/store/src/server/ntx_builder.rs @@ -3,6 +3,7 @@ use std::num::{NonZero, TryFromIntError}; use miden_crypto::merkle::smt::SmtProof; use miden_node_proto::domain::account::AccountInfo; +use miden_node_proto::errors::ConversionError; use miden_node_proto::generated as proto; use miden_node_proto::generated::rpc::BlockRange; use miden_node_proto::generated::store::ntx_builder_server; @@ -172,7 +173,9 @@ impl ntx_builder_server::NtxBuilder for StoreApi { ) -> Result, Status> { debug!(target: COMPONENT, ?request); let request = request.into_inner(); - let account_request = request.try_into().map_err(GetAccountError::DeserializationFailed)?; + let account_request = request + .try_into() + .map_err(|e| GetAccountError::DeserializationFailed(ConversionError::from(e)))?; let proof = self.state.get_account(account_request).await?; diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index bb3098fffa..5595f75679 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -1,6 +1,6 @@ use miden_node_proto::convert; use miden_node_proto::domain::block::InvalidBlockRange; -use miden_node_proto::errors::MissingFieldHelper; +use miden_node_proto::errors::{ConversionError, MissingFieldHelper}; use miden_node_proto::generated::store::rpc_server; use miden_node_proto::generated::{self as proto}; use miden_node_utils::limiter::{ @@ -164,7 +164,7 @@ impl rpc_server::Rpc for StoreApi { let block_range = request .block_range .ok_or_else(|| proto::rpc::SyncChainMmrRequest::missing_field(stringify!(block_range))) - .map_err(SyncChainMmrError::DeserializationFailed)?; + .map_err(|e| SyncChainMmrError::DeserializationFailed(ConversionError::from(e)))?; let block_from = BlockNumber::from(block_range.block_from); if block_from > chain_tip { @@ -246,7 +246,9 @@ impl rpc_server::Rpc for StoreApi { ) -> Result, Status> { debug!(target: COMPONENT, ?request); let request = request.into_inner(); - let account_request = request.try_into().map_err(GetAccountError::DeserializationFailed)?; + let account_request = request + .try_into() + .map_err(|e| GetAccountError::DeserializationFailed(ConversionError::from(e)))?; let account_data = self.state.get_account(account_request).await?; From 26dc4974439b7e81564a48b5a8654b331b06f281 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 13:21:50 +1300 Subject: [PATCH 02/26] ConversionError struct --- crates/block-producer/src/store/mod.rs | 36 ++- crates/ntx-builder/src/clients/store.rs | 63 ++---- crates/proto/src/domain/account.rs | 245 ++++++++++---------- crates/proto/src/domain/batch.rs | 40 +--- crates/proto/src/domain/block.rs | 143 ++++++------ crates/proto/src/domain/digest.rs | 53 ++--- crates/proto/src/domain/mempool.rs | 71 ++---- crates/proto/src/domain/merkle.rs | 84 +++---- crates/proto/src/domain/note.rs | 108 ++++----- crates/proto/src/domain/nullifier.rs | 43 +--- crates/proto/src/domain/transaction.rs | 34 +-- crates/proto/src/errors/mod.rs | 259 ++++++++++++++++------ crates/rpc/src/server/api.rs | 13 +- crates/store/src/server/api.rs | 46 ++-- crates/store/src/server/block_producer.rs | 20 +- crates/store/src/server/ntx_builder.rs | 5 +- crates/store/src/server/rpc_api.rs | 14 +- 17 files changed, 587 insertions(+), 690 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index 14c59f13e4..79da468f53 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -5,7 +5,7 @@ use std::num::NonZeroU32; use itertools::Itertools; use miden_node_proto::clients::{Builder, StoreBlockProducerClient}; use miden_node_proto::domain::batch::BatchInputs; -use miden_node_proto::errors::{ConversionError, MissingFieldHelper}; +use miden_node_proto::errors::ConversionError; use miden_node_proto::{AccountState, generated as proto}; use miden_node_utils::formatting::format_opt; use miden_protocol::Word; @@ -72,18 +72,18 @@ impl TryFrom for TransactionInputs { fn try_from(response: proto::store::TransactionInputs) -> Result { let AccountState { account_id, account_commitment } = response .account_state - .ok_or(proto::store::TransactionInputs::missing_field(stringify!(account_state)))? + .ok_or(ConversionError::missing_field::(stringify!( + account_state + )))? .try_into()?; let mut nullifiers = HashMap::new(); for nullifier_record in response.nullifiers { let nullifier = nullifier_record .nullifier - .ok_or( - proto::store::transaction_inputs::NullifierTransactionInputRecord::missing_field( - stringify!(nullifier), - ), - )? + .ok_or(ConversionError::missing_field::< + proto::store::transaction_inputs::NullifierTransactionInputRecord, + >(stringify!(nullifier)))? .try_into()?; // Note that this intentionally maps 0 to None as this is the definition used in @@ -94,7 +94,7 @@ impl TryFrom for TransactionInputs { let found_unauthenticated_notes = response .found_unauthenticated_notes .into_iter() - .map(|d| Word::try_from(d).map_err(ConversionError::from)) + .map(Word::try_from) .collect::>()?; let current_block_height = response.block_height.into(); @@ -149,16 +149,12 @@ impl StoreClient { .into_inner() .block_header .ok_or_else(|| { - StoreError::DeserializationError( - miden_node_proto::generated::blockchain::BlockHeader::missing_field( - "block_header", - ) - .into(), - ) + StoreError::DeserializationError(ConversionError::missing_field::< + miden_node_proto::generated::blockchain::BlockHeader, + >("block_header")) })?; - BlockHeader::try_from(response) - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) + BlockHeader::try_from(response).map_err(StoreError::DeserializationError) } #[instrument(target = COMPONENT, name = "store.client.get_tx_inputs", skip_all, err)] @@ -225,9 +221,7 @@ impl StoreClient { let store_response = self.client.clone().get_block_inputs(request).await?.into_inner(); - store_response - .try_into() - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) + store_response.try_into().map_err(StoreError::DeserializationError) } #[instrument(target = COMPONENT, name = "store.client.get_batch_inputs", skip_all, err)] @@ -243,9 +237,7 @@ impl StoreClient { let store_response = self.client.clone().get_batch_inputs(request).await?.into_inner(); - store_response - .try_into() - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) + store_response.try_into().map_err(StoreError::DeserializationError) } #[instrument(target = COMPONENT, name = "store.client.apply_block", skip_all, err)] diff --git a/crates/ntx-builder/src/clients/store.rs b/crates/ntx-builder/src/clients/store.rs index 0556c887b0..435e499cf3 100644 --- a/crates/ntx-builder/src/clients/store.rs +++ b/crates/ntx-builder/src/clients/store.rs @@ -4,12 +4,7 @@ use std::time::Duration; use miden_node_proto::clients::{Builder, StoreNtxBuilderClient}; use miden_node_proto::domain::account::{AccountDetails, AccountResponse, NetworkAccountId}; -use miden_node_proto::errors::{ - AccountConversionError, - ConversionError, - DigestConversionError, - ProtoConversionError, -}; +use miden_node_proto::errors::ConversionError; use miden_node_proto::generated::rpc::BlockRange; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -108,11 +103,9 @@ impl StoreClient { Some(block) => { let peaks: Vec = try_convert(response.current_peaks) .collect::>() - .map_err(|e: DigestConversionError| { - StoreError::DeserializationError(ConversionError::from(e)) - })?; - let header = BlockHeader::try_from(block) - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; + .map_err(StoreError::DeserializationError)?; + let header = + BlockHeader::try_from(block).map_err(StoreError::DeserializationError)?; let peaks = MmrPeaks::new(Forest::new(header.block_num().as_usize()), peaks) .map_err(|_| { @@ -149,9 +142,7 @@ impl StoreClient { // which implies details being public, so OK to error otherwise let account = match store_response.map(|acc| acc.details) { Some(Some(details)) => Some(Account::read_from_bytes(&details).map_err(|err| { - StoreError::DeserializationError( - ProtoConversionError::deserialization_error("account", err).into(), - ) + StoreError::DeserializationError(ConversionError::deserialization("account", err)) })?), _ => None, }; @@ -187,15 +178,15 @@ impl StoreClient { let proto_response = self.inner.clone().get_account(proto_request).await?.into_inner(); // Convert proto response to domain type. - let account_response = AccountResponse::try_from(proto_response) - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; + let account_response = + AccountResponse::try_from(proto_response).map_err(StoreError::DeserializationError)?; // Build partial account. let account_details = account_response .details .ok_or(StoreError::MissingDetails("account details".into()))?; let partial_account = build_minimal_foreign_account(&account_details) - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; + .map_err(StoreError::DeserializationError)?; Ok(AccountInputs::new(partial_account, account_response.witness)) } @@ -228,7 +219,7 @@ impl StoreClient { for note in resp.notes { all_notes.push( AccountTargetNetworkNote::try_from(note) - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?, + .map_err(StoreError::DeserializationError)?, ); } @@ -330,9 +321,10 @@ impl StoreClient { .into_iter() .map(|account_id| { let account_id = AccountId::read_from_bytes(&account_id.id).map_err(|err| { - StoreError::DeserializationError( - ProtoConversionError::deserialization_error("account_id", err).into(), - ) + StoreError::DeserializationError(ConversionError::deserialization( + "account_id", + err, + )) })?; NetworkAccountId::try_from(account_id).map_err(|_| { StoreError::MalformedResponse( @@ -342,12 +334,9 @@ impl StoreClient { }) .collect::, StoreError>>()?; - let pagination_info = response.pagination_info.ok_or(ConversionError::from( - ProtoConversionError::MissingField { - entity: "NetworkAccountIdList", - field_name: "pagination_info", - }, - ))?; + let pagination_info = response.pagination_info.ok_or(ConversionError::missing_field::< + proto::store::NetworkAccountIdList, + >("pagination_info"))?; Ok((accounts, pagination_info)) } @@ -385,7 +374,7 @@ impl StoreClient { script .map(NoteScript::try_from) .transpose() - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e))) + .map_err(StoreError::DeserializationError) } #[instrument(target = COMPONENT, name = "store.client.get_vault_asset_witnesses", skip_all, err)] @@ -418,12 +407,10 @@ impl StoreClient { let smt_opening = asset_witness.proof.ok_or_else(|| { StoreError::MalformedResponse("missing proof in vault asset witness".to_string()) })?; - let proof: SmtProof = smt_opening - .try_into() - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; - let witness = AssetWitness::new(proof).map_err(|err| { - StoreError::DeserializationError(AccountConversionError::from(err).into()) - })?; + let proof: SmtProof = + smt_opening.try_into().map_err(StoreError::DeserializationError)?; + let witness = AssetWitness::new(proof) + .map_err(|err| StoreError::DeserializationError(ConversionError::from(err)))?; asset_witnesses.push(witness); } @@ -459,9 +446,7 @@ impl StoreClient { StoreError::MalformedResponse("missing proof in storage map witness".to_string()) })?; - let proof: SmtProof = smt_opening - .try_into() - .map_err(|e| StoreError::DeserializationError(ConversionError::from(e)))?; + let proof: SmtProof = smt_opening.try_into().map_err(StoreError::DeserializationError)?; // Create the storage map witness using the proof and raw map key. let witness = StorageMapWitness::new(proof, [map_key]).map_err(|_err| { @@ -496,12 +481,12 @@ pub enum StoreError { /// to retrieve foreign account data during transaction execution. pub fn build_minimal_foreign_account( account_details: &AccountDetails, -) -> Result { +) -> Result { // Derive account code. let account_code_bytes = account_details .account_code .as_ref() - .ok_or(AccountConversionError::AccountCodeMissing)?; + .ok_or_else(|| ConversionError::message("account code missing"))?; let account_code = AccountCode::from_bytes(account_code_bytes)?; // Derive partial storage. Storage maps are not required for foreign accounts. diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 99b48ffb41..25ed44ea2d 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -19,49 +19,15 @@ use miden_protocol::block::BlockNumber; use miden_protocol::block::account_tree::AccountWitness; use miden_protocol::crypto::merkle::SparseMerklePath; use miden_protocol::crypto::merkle::smt::SmtProof; -use miden_protocol::errors::{AccountError, AssetError, StorageSlotNameError}; use miden_protocol::note::NoteAttachment; use miden_protocol::utils::{Deserializable, DeserializationError, Serializable}; use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError}; use thiserror::Error; use super::try_convert; -use crate::domain::digest::DigestConversionError; -use crate::domain::merkle::MerkleConversionError; -use crate::errors::{MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::generated::{self as proto}; -// ACCOUNT CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum AccountConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Digest(#[from] DigestConversionError), - #[error(transparent)] - Merkle(#[from] MerkleConversionError), - #[error("account error")] - AccountError(#[from] AccountError), - #[error("asset error")] - AssetError(#[from] AssetError), - #[error("storage slot name error")] - StorageSlotNameError(#[from] StorageSlotNameError), - #[error("enum variant discriminant out of range")] - EnumDiscriminantOutOfRange, - #[error("value is not in the range 0..MODULUS")] - NotAValidFelt, - #[error("account code missing")] - AccountCodeMissing, -} - -impl From for tonic::Status { - fn from(value: AccountConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - #[cfg(test)] mod tests; @@ -88,11 +54,11 @@ impl Debug for proto::account::AccountId { // ------------------------------------------------------------------------------------------------ impl TryFrom for AccountId { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(account_id: proto::account::AccountId) -> Result { AccountId::read_from_bytes(&account_id.id) - .map_err(|_| AccountConversionError::NotAValidFelt) + .map_err(|_| ConversionError::message("value is not in the range 0..MODULUS")) } } @@ -150,7 +116,7 @@ impl From<&AccountInfo> for proto::account::AccountDetails { //================================================================================================ impl TryFrom for AccountStorageHeader { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::account::AccountStorageHeader) -> Result { let proto::account::AccountStorageHeader { slots } = value; @@ -160,11 +126,13 @@ impl TryFrom for AccountStorageHeader { .map(|slot| { let slot_name = StorageSlotName::new(slot.slot_name)?; let slot_type = storage_slot_type_from_raw(slot.slot_type)?; - let commitment = - slot.commitment.ok_or(AccountConversionError::NotAValidFelt)?.try_into()?; + let commitment = slot + .commitment + .ok_or(ConversionError::message("value is not in the range 0..MODULUS"))? + .try_into()?; Ok(StorageSlotHeader::new(slot_name, slot_type, commitment)) }) - .collect::, AccountConversionError>>()?; + .collect::, ConversionError>>()?; Ok(AccountStorageHeader::new(slot_headers)?) } @@ -182,13 +150,15 @@ pub struct AccountRequest { } impl TryFrom for AccountRequest { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::rpc::AccountRequest) -> Result { let proto::rpc::AccountRequest { account_id, block_num, details } = value; let account_id = account_id - .ok_or(proto::rpc::AccountRequest::missing_field(stringify!(account_id)))? + .ok_or(ConversionError::missing_field::(stringify!( + account_id + )))? .try_into()?; let block_num = block_num.map(Into::into); @@ -206,7 +176,7 @@ pub struct AccountDetailRequest { } impl TryFrom for AccountDetailRequest { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from( value: proto::rpc::account_request::AccountDetailRequest, @@ -238,7 +208,7 @@ pub struct StorageMapRequest { impl TryFrom for StorageMapRequest { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from( value: proto::rpc::account_request::account_detail_request::StorageMapDetailRequest, @@ -249,7 +219,11 @@ impl TryFrom(stringify!(slot_data)))? + .try_into()?; Ok(StorageMapRequest { slot_name, slot_data }) } @@ -267,7 +241,7 @@ impl proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData, > for SlotData { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from( value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData, @@ -277,7 +251,7 @@ impl Ok(match value { ProtoSlotData::AllEntries(true) => SlotData::All, ProtoSlotData::AllEntries(false) => { - return Err(AccountConversionError::EnumDiscriminantOutOfRange); + return Err(ConversionError::message("enum variant discriminant out of range")); }, ProtoSlotData::MapKeys(keys) => { let keys = try_convert(keys.map_keys).collect::, _>>()?; @@ -291,7 +265,7 @@ impl //================================================================================================ impl TryFrom for AccountHeader { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::account::AccountHeader) -> Result { let proto::account::AccountHeader { @@ -303,18 +277,28 @@ impl TryFrom for AccountHeader { } = value; let account_id = account_id - .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))? + .ok_or(ConversionError::missing_field::(stringify!( + account_id + )))? .try_into()?; let vault_root = vault_root - .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))? + .ok_or(ConversionError::missing_field::(stringify!( + vault_root + )))? .try_into()?; let storage_commitment = storage_commitment - .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))? + .ok_or(ConversionError::missing_field::(stringify!( + storage_commitment + )))? .try_into()?; let code_commitment = code_commitment - .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))? + .ok_or(ConversionError::missing_field::(stringify!( + code_commitment + )))? .try_into()?; - let nonce = nonce.try_into().map_err(|_e| AccountConversionError::NotAValidFelt)?; + let nonce = nonce + .try_into() + .map_err(|_e| ConversionError::message("value is not in the range 0..MODULUS"))?; Ok(AccountHeader::new( account_id, @@ -400,7 +384,7 @@ impl AccountVaultDetails { } impl TryFrom for AccountVaultDetails { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::rpc::AccountVaultDetails) -> Result { let proto::rpc::AccountVaultDetails { too_many_assets, assets } = value; @@ -408,15 +392,14 @@ impl TryFrom for AccountVaultDetails { if too_many_assets { Ok(Self::LimitExceeded) } else { - let parsed_assets = Result::, AccountConversionError>::from_iter( - assets.into_iter().map(|asset| { - let asset = asset - .asset - .ok_or(proto::primitives::Asset::missing_field(stringify!(asset)))?; + let parsed_assets = + Result::, ConversionError>::from_iter(assets.into_iter().map(|asset| { + let asset = asset.asset.ok_or(ConversionError::missing_field::< + proto::primitives::Asset, + >(stringify!(asset)))?; let asset = Word::try_from(asset)?; - Asset::try_from(asset).map_err(AccountConversionError::AssetError) - }), - )?; + Asset::try_from(asset).map_err(ConversionError::from) + }))?; Ok(Self::Assets(parsed_assets)) } } @@ -552,7 +535,7 @@ impl AccountStorageMapDetails { impl TryFrom for AccountStorageMapDetails { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from( value: proto::rpc::account_storage_details::AccountStorageMapDetails, @@ -578,12 +561,9 @@ impl TryFrom } else { match entries { None => { - return Err( - proto::rpc::account_storage_details::AccountStorageMapDetails::missing_field( - stringify!(entries), - ) - .into(), - ); + return Err(ConversionError::missing_field::< + proto::rpc::account_storage_details::AccountStorageMapDetails, + >(stringify!(entries))); }, Some(ProtoEntries::AllEntries(AllMapEntries { entries })) => { let entries = entries @@ -591,28 +571,35 @@ impl TryFrom .map(|entry| { let key = entry .key - .ok_or(StorageMapEntry::missing_field(stringify!(key)))? + .ok_or(ConversionError::missing_field::( + stringify!(key), + ))? .try_into() .map(StorageMapKey::new)?; let value = entry .value - .ok_or(StorageMapEntry::missing_field(stringify!(value)))? + .ok_or(ConversionError::missing_field::( + stringify!(value), + ))? .try_into()?; Ok((key, value)) }) - .collect::, AccountConversionError>>()?; + .collect::, ConversionError>>()?; StorageMapEntries::AllEntries(entries) }, Some(ProtoEntries::EntriesWithProofs(MapEntriesWithProofs { entries })) => { let proofs = entries .into_iter() .map(|entry| { - let smt_opening = entry.proof.ok_or( - StorageMapEntryWithProof::missing_field(stringify!(proof)), - )?; - SmtProof::try_from(smt_opening).map_err(AccountConversionError::from) + let smt_opening = + entry.proof.ok_or(ConversionError::missing_field::< + StorageMapEntryWithProof, + >(stringify!( + proof + )))?; + SmtProof::try_from(smt_opening) }) - .collect::, AccountConversionError>>()?; + .collect::, ConversionError>>()?; StorageMapEntries::EntriesWithProofs(proofs) }, } @@ -705,13 +692,15 @@ impl AccountStorageDetails { } impl TryFrom for AccountStorageDetails { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::rpc::AccountStorageDetails) -> Result { let proto::rpc::AccountStorageDetails { header, map_details } = value; let header = header - .ok_or(proto::rpc::AccountStorageDetails::missing_field(stringify!(header)))? + .ok_or(ConversionError::missing_field::( + stringify!(header), + ))? .try_into()?; let map_details = try_convert(map_details).collect::, _>>()?; @@ -731,13 +720,11 @@ impl From for proto::rpc::AccountStorageDetails { } } -const fn storage_slot_type_from_raw( - slot_type: u32, -) -> Result { +fn storage_slot_type_from_raw(slot_type: u32) -> Result { Ok(match slot_type { 0 => StorageSlotType::Value, 1 => StorageSlotType::Map, - _ => return Err(AccountConversionError::EnumDiscriminantOutOfRange), + _ => return Err(ConversionError::message("enum variant discriminant out of range")), }) } @@ -759,17 +746,21 @@ pub struct AccountResponse { } impl TryFrom for AccountResponse { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::rpc::AccountResponse) -> Result { let proto::rpc::AccountResponse { block_num, witness, details } = value; let block_num = block_num - .ok_or(proto::rpc::AccountResponse::missing_field(stringify!(block_num)))? + .ok_or(ConversionError::missing_field::(stringify!( + block_num + )))? .into(); let witness = witness - .ok_or(proto::rpc::AccountResponse::missing_field(stringify!(witness)))? + .ok_or(ConversionError::missing_field::(stringify!( + witness + )))? .try_into()?; let details = details.map(TryFrom::try_from).transpose()?; @@ -820,7 +811,7 @@ impl AccountDetails { } impl TryFrom for AccountDetails { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result { let proto::rpc::account_response::AccountDetails { @@ -831,19 +822,21 @@ impl TryFrom for AccountDetails { } = value; let account_header = header - .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(header)))? + .ok_or(ConversionError::missing_field::( + stringify!(header), + ))? .try_into()?; let storage_details = storage_details - .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!( - storage_details - )))? + .ok_or(ConversionError::missing_field::( + stringify!(storage_details), + ))? .try_into()?; let vault_details = vault_details - .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!( - vault_details - )))? + .ok_or(ConversionError::missing_field::( + stringify!(vault_details), + ))? .try_into()?; let account_code = code; @@ -883,28 +876,34 @@ impl From for proto::rpc::account_response::AccountDetails { // ================================================================================================ impl TryFrom for AccountWitness { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(account_witness: proto::account::AccountWitness) -> Result { let witness_id = account_witness .witness_id - .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))? + .ok_or(ConversionError::missing_field::(stringify!( + witness_id + )))? .try_into()?; let commitment = account_witness .commitment - .ok_or(proto::account::AccountWitness::missing_field(stringify!(commitment)))? + .ok_or(ConversionError::missing_field::(stringify!( + commitment + )))? .try_into()?; let path = account_witness .path - .ok_or(proto::account::AccountWitness::missing_field(stringify!(path)))? + .ok_or(ConversionError::missing_field::(stringify!( + path + )))? .try_into()?; - Ok(AccountWitness::new(witness_id, commitment, path).map_err(|err| { - ProtoConversionError::deserialization_error( + AccountWitness::new(witness_id, commitment, path).map_err(|err| { + ConversionError::deserialization( "AccountWitness", DeserializationError::InvalidValue(err.to_string()), ) - })?) + }) } } @@ -929,28 +928,34 @@ pub struct AccountWitnessRecord { } impl TryFrom for AccountWitnessRecord { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from( account_witness_record: proto::account::AccountWitness, ) -> Result { let witness_id = account_witness_record .witness_id - .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))? + .ok_or(ConversionError::missing_field::(stringify!( + witness_id + )))? .try_into()?; let commitment = account_witness_record .commitment - .ok_or(proto::account::AccountWitness::missing_field(stringify!(commitment)))? + .ok_or(ConversionError::missing_field::(stringify!( + commitment + )))? .try_into()?; let path: SparseMerklePath = account_witness_record .path .as_ref() - .ok_or(proto::account::AccountWitness::missing_field(stringify!(path)))? + .ok_or(ConversionError::missing_field::(stringify!( + path + )))? .clone() .try_into()?; let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| { - ProtoConversionError::deserialization_error( + ConversionError::deserialization( "AccountWitness", DeserializationError::InvalidValue(err.to_string()), ) @@ -959,7 +964,9 @@ impl TryFrom for AccountWitnessRecord { Ok(Self { account_id: account_witness_record .account_id - .ok_or(proto::account::AccountWitness::missing_field(stringify!(account_id)))? + .ok_or(ConversionError::missing_field::( + stringify!(account_id), + ))? .try_into()?, witness, }) @@ -1000,23 +1007,23 @@ impl Display for AccountState { } impl TryFrom for AccountState { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from( from: proto::store::transaction_inputs::AccountTransactionInputRecord, ) -> Result { let account_id = from .account_id - .ok_or(proto::store::transaction_inputs::AccountTransactionInputRecord::missing_field( - stringify!(account_id), - ))? + .ok_or(ConversionError::missing_field::< + proto::store::transaction_inputs::AccountTransactionInputRecord, + >(stringify!(account_id)))? .try_into()?; let account_commitment = from .account_commitment - .ok_or(proto::store::transaction_inputs::AccountTransactionInputRecord::missing_field( - stringify!(account_commitment), - ))? + .ok_or(ConversionError::missing_field::< + proto::store::transaction_inputs::AccountTransactionInputRecord, + >(stringify!(account_commitment)))? .try_into()?; // If the commitment is equal to `Word::empty()`, it signifies that this is a new @@ -1044,13 +1051,15 @@ impl From for proto::store::transaction_inputs::AccountTransaction // ================================================================================================ impl TryFrom for Asset { - type Error = AccountConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::Asset) -> Result { - let inner = value.asset.ok_or(proto::primitives::Asset::missing_field("asset"))?; + let inner = value + .asset + .ok_or(ConversionError::missing_field::("asset"))?; let word = Word::try_from(inner)?; - Asset::try_from(word).map_err(AccountConversionError::AssetError) + Asset::try_from(word).map_err(ConversionError::from) } } diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index 0e26b885f1..8e2acb9586 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -4,32 +4,10 @@ use miden_protocol::block::BlockHeader; use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; -use thiserror::Error; -use crate::domain::block::BlockConversionError; -use crate::domain::note::NoteConversionError; -use crate::errors::{MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::generated as proto; -// BATCH CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum BatchConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Block(#[from] BlockConversionError), - #[error(transparent)] - Note(#[from] NoteConversionError), -} - -impl From for tonic::Status { - fn from(value: BatchConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - /// Data required for a transaction batch. #[derive(Clone, Debug)] pub struct BatchInputs { @@ -49,25 +27,21 @@ impl From for proto::store::BatchInputs { } impl TryFrom for BatchInputs { - type Error = BatchConversionError; + type Error = ConversionError; - fn try_from(response: proto::store::BatchInputs) -> Result { + fn try_from(response: proto::store::BatchInputs) -> Result { let result = Self { batch_reference_block_header: response .batch_reference_block_header - .ok_or(proto::store::BatchInputs::missing_field("block_header"))? + .ok_or(ConversionError::missing_field::("block_header"))? .try_into()?, note_proofs: response .note_proofs .iter() - .map(|p| { - <(NoteId, NoteInclusionProof)>::try_from(p).map_err(BatchConversionError::from) - }) - .collect::>()?, + .map(<(NoteId, NoteInclusionProof)>::try_from) + .collect::>()?, partial_block_chain: PartialBlockchain::read_from_bytes(&response.partial_block_chain) - .map_err(|source| { - ProtoConversionError::deserialization_error("PartialBlockchain", source) - })?, + .map_err(|source| ConversionError::deserialization("PartialBlockchain", source))?, }; Ok(result) diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 80394286d8..7e6b981ebc 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -12,49 +12,14 @@ use miden_protocol::block::{ SignedBlock, }; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; -use miden_protocol::errors::{AccountError, FeeError}; use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; use thiserror::Error; -use crate::domain::account::AccountConversionError; -use crate::domain::digest::DigestConversionError; -use crate::domain::merkle::MerkleConversionError; -use crate::domain::note::NoteConversionError; -use crate::domain::nullifier::NullifierConversionError; -use crate::errors::{MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; -// BLOCK CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum BlockConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Digest(#[from] DigestConversionError), - #[error(transparent)] - Account(#[from] AccountConversionError), - #[error(transparent)] - Merkle(#[from] MerkleConversionError), - #[error(transparent)] - Note(#[from] NoteConversionError), - #[error(transparent)] - Nullifier(#[from] NullifierConversionError), - #[error("fee parameters error")] - FeeError(#[from] FeeError), - #[error("account error")] - AccountError(#[from] AccountError), -} - -impl From for tonic::Status { - fn from(value: BlockConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - // BLOCK NUMBER // ================================================================================================ @@ -99,7 +64,7 @@ impl From for proto::blockchain::BlockHeader { } impl TryFrom<&proto::blockchain::BlockHeader> for BlockHeader { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(value: &proto::blockchain::BlockHeader) -> Result { value.try_into() @@ -107,50 +72,64 @@ impl TryFrom<&proto::blockchain::BlockHeader> for BlockHeader { } impl TryFrom for BlockHeader { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(value: proto::blockchain::BlockHeader) -> Result { Ok(BlockHeader::new( value.version, value .prev_block_commitment - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!( - prev_block_commitment - )))? + .ok_or(ConversionError::missing_field::( + stringify!(prev_block_commitment), + ))? .try_into()?, value.block_num.into(), value .chain_commitment - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!(chain_commitment)))? + .ok_or(ConversionError::missing_field::( + stringify!(chain_commitment), + ))? .try_into()?, value .account_root - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!(account_root)))? + .ok_or(ConversionError::missing_field::( + stringify!(account_root), + ))? .try_into()?, value .nullifier_root - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!(nullifier_root)))? + .ok_or(ConversionError::missing_field::( + stringify!(nullifier_root), + ))? .try_into()?, value .note_root - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!(note_root)))? + .ok_or(ConversionError::missing_field::( + stringify!(note_root), + ))? .try_into()?, value .tx_commitment - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!(tx_commitment)))? + .ok_or(ConversionError::missing_field::( + stringify!(tx_commitment), + ))? .try_into()?, value .tx_kernel_commitment - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!( - tx_kernel_commitment - )))? + .ok_or(ConversionError::missing_field::( + stringify!(tx_kernel_commitment), + ))? .try_into()?, value .validator_key - .ok_or(proto::blockchain::BlockHeader::missing_field(stringify!(validator_key)))? + .ok_or(ConversionError::missing_field::( + stringify!(validator_key), + ))? .try_into()?, FeeParameters::try_from(value.fee_parameters.ok_or( - proto::blockchain::FeeParameters::missing_field(stringify!(fee_parameters)), + ConversionError::missing_field::(stringify!( + fee_parameters + )), )?)?, value.timestamp, )) @@ -173,7 +152,7 @@ impl From for proto::blockchain::BlockBody { } impl TryFrom<&proto::blockchain::BlockBody> for BlockBody { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(value: &proto::blockchain::BlockBody) -> Result { value.try_into() @@ -181,10 +160,10 @@ impl TryFrom<&proto::blockchain::BlockBody> for BlockBody { } impl TryFrom for BlockBody { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(value: proto::blockchain::BlockBody) -> Result { - Ok(BlockBody::read_from_bytes(&value.block_body) - .map_err(|source| ProtoConversionError::deserialization_error("BlockBody", source))?) + BlockBody::read_from_bytes(&value.block_body) + .map_err(|source| ConversionError::deserialization("BlockBody", source)) } } @@ -208,7 +187,7 @@ impl From for proto::blockchain::SignedBlock { } impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(value: &proto::blockchain::SignedBlock) -> Result { value.try_into() @@ -216,19 +195,25 @@ impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { } impl TryFrom for SignedBlock { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(value: proto::blockchain::SignedBlock) -> Result { let header = value .header - .ok_or(proto::blockchain::SignedBlock::missing_field(stringify!(header)))? + .ok_or(ConversionError::missing_field::(stringify!( + header + )))? .try_into()?; let body = value .body - .ok_or(proto::blockchain::SignedBlock::missing_field(stringify!(body)))? + .ok_or(ConversionError::missing_field::(stringify!( + body + )))? .try_into()?; let signature = value .signature - .ok_or(proto::blockchain::SignedBlock::missing_field(stringify!(signature)))? + .ok_or(ConversionError::missing_field::(stringify!( + signature + )))? .try_into()?; Ok(SignedBlock::new_unchecked(header, body, signature)) @@ -271,12 +256,14 @@ impl From for proto::store::BlockInputs { } impl TryFrom for BlockInputs { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(response: proto::store::BlockInputs) -> Result { let latest_block_header: BlockHeader = response .latest_block_header - .ok_or(proto::blockchain::BlockHeader::missing_field("block_header"))? + .ok_or(ConversionError::missing_field::( + "block_header", + ))? .try_into()?; let account_witnesses = response @@ -286,7 +273,7 @@ impl TryFrom for BlockInputs { let witness_record: AccountWitnessRecord = entry.try_into()?; Ok((witness_record.account_id, witness_record.witness)) }) - .collect::, BlockConversionError>>()?; + .collect::, ConversionError>>()?; let nullifier_witnesses = response .nullifier_witnesses @@ -295,20 +282,16 @@ impl TryFrom for BlockInputs { let witness: NullifierWitnessRecord = entry.try_into()?; Ok((witness.nullifier, NullifierWitness::new(witness.proof))) }) - .collect::, BlockConversionError>>()?; + .collect::, ConversionError>>()?; let unauthenticated_note_proofs = response .unauthenticated_note_proofs .iter() - .map(|p| { - <(NoteId, NoteInclusionProof)>::try_from(p).map_err(BlockConversionError::from) - }) - .collect::>()?; + .map(<(NoteId, NoteInclusionProof)>::try_from) + .collect::>()?; let partial_block_chain = PartialBlockchain::read_from_bytes(&response.partial_block_chain) - .map_err(|source| { - ProtoConversionError::deserialization_error("PartialBlockchain", source) - })?; + .map_err(|source| ConversionError::deserialization("PartialBlockchain", source))?; Ok(BlockInputs::new( latest_block_header, @@ -324,10 +307,10 @@ impl TryFrom for BlockInputs { // ================================================================================================ impl TryFrom for PublicKey { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(public_key: proto::blockchain::ValidatorPublicKey) -> Result { - Ok(PublicKey::read_from_bytes(&public_key.validator_key) - .map_err(|source| ProtoConversionError::deserialization_error("PublicKey", source))?) + PublicKey::read_from_bytes(&public_key.validator_key) + .map_err(|source| ConversionError::deserialization("PublicKey", source)) } } @@ -347,10 +330,10 @@ impl From<&PublicKey> for proto::blockchain::ValidatorPublicKey { // ================================================================================================ impl TryFrom for Signature { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(signature: proto::blockchain::BlockSignature) -> Result { - Ok(Signature::read_from_bytes(&signature.signature) - .map_err(|source| ProtoConversionError::deserialization_error("Signature", source))?) + Signature::read_from_bytes(&signature.signature) + .map_err(|source| ConversionError::deserialization("Signature", source)) } } @@ -370,10 +353,12 @@ impl From<&Signature> for proto::blockchain::BlockSignature { // ================================================================================================ impl TryFrom for FeeParameters { - type Error = BlockConversionError; + type Error = ConversionError; fn try_from(fee_params: proto::blockchain::FeeParameters) -> Result { let native_asset_id = fee_params.native_asset_id.map(AccountId::try_from).ok_or( - proto::blockchain::FeeParameters::missing_field(stringify!(native_asset_id)), + ConversionError::missing_field::(stringify!( + native_asset_id + )), )??; let fee_params = FeeParameters::new(native_asset_id, fee_params.verification_base_fee)?; Ok(fee_params) diff --git a/crates/proto/src/domain/digest.rs b/crates/proto/src/domain/digest.rs index 5a8e1bdcc0..2815cef093 100644 --- a/crates/proto/src/domain/digest.rs +++ b/crates/proto/src/domain/digest.rs @@ -4,34 +4,10 @@ use hex::{FromHex, ToHex}; use miden_protocol::account::StorageMapKey; use miden_protocol::note::NoteId; use miden_protocol::{Felt, StarkField, Word}; -use thiserror::Error; -use crate::errors::ProtoConversionError; +use crate::errors::ConversionError; use crate::generated as proto; -// DIGEST CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum DigestConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error("hex error")] - HexError(#[from] hex::FromHexError), - #[error("too much data, expected {expected}, got {got}")] - TooMuchData { expected: usize, got: usize }, - #[error("not enough data, expected {expected}, got {got}")] - InsufficientData { expected: usize, got: usize }, - #[error("value is not in the range 0..MODULUS")] - NotAValidFelt, -} - -impl From for tonic::Status { - fn from(value: DigestConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - // CONSTANTS // ================================================================================================ @@ -83,19 +59,18 @@ impl ToHex for proto::primitives::Digest { } impl FromHex for proto::primitives::Digest { - type Error = DigestConversionError; + type Error = ConversionError; fn from_hex>(hex: T) -> Result { let data = hex::decode(hex)?; match data.len() { - size if size < DIGEST_DATA_SIZE => Err(DigestConversionError::InsufficientData { - expected: DIGEST_DATA_SIZE, - got: size, - }), - size if size > DIGEST_DATA_SIZE => { - Err(DigestConversionError::TooMuchData { expected: DIGEST_DATA_SIZE, got: size }) - }, + size if size < DIGEST_DATA_SIZE => Err(ConversionError::message(format!( + "not enough data, expected {DIGEST_DATA_SIZE}, got {size}" + ))), + size if size > DIGEST_DATA_SIZE => Err(ConversionError::message(format!( + "too much data, expected {DIGEST_DATA_SIZE}, got {size}" + ))), _ => { let d0 = u64::from_be_bytes(data[..8].try_into().unwrap()); let d1 = u64::from_be_bytes(data[8..16].try_into().unwrap()); @@ -196,14 +171,14 @@ impl From for [u64; 4] { } impl TryFrom for [Felt; 4] { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::Digest) -> Result { if [value.d0, value.d1, value.d2, value.d3] .iter() .any(|v| *v >= ::MODULUS) { - return Err(DigestConversionError::NotAValidFelt); + return Err(ConversionError::message("value is not in the range 0..MODULUS")); } Ok([ @@ -216,7 +191,7 @@ impl TryFrom for [Felt; 4] { } impl TryFrom for Word { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::Digest) -> Result { Ok(Self::new(value.try_into()?)) @@ -224,7 +199,7 @@ impl TryFrom for Word { } impl TryFrom for StorageMapKey { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::Digest) -> Result { Ok(StorageMapKey::new(value.try_into()?)) @@ -232,7 +207,7 @@ impl TryFrom for StorageMapKey { } impl TryFrom<&proto::primitives::Digest> for [Felt; 4] { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(value: &proto::primitives::Digest) -> Result { (*value).try_into() @@ -240,7 +215,7 @@ impl TryFrom<&proto::primitives::Digest> for [Felt; 4] { } impl TryFrom<&proto::primitives::Digest> for Word { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(value: &proto::primitives::Digest) -> Result { (*value).try_into() diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index c08720f818..ae39ec8dd5 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -6,38 +6,10 @@ use miden_protocol::note::Nullifier; use miden_protocol::transaction::TransactionId; use miden_protocol::utils::{Deserializable, Serializable}; use miden_standards::note::AccountTargetNetworkNote; -use thiserror::Error; -use crate::domain::block::BlockConversionError; -use crate::domain::digest::DigestConversionError; -use crate::domain::note::NoteConversionError; -use crate::domain::transaction::TransactionConversionError; -use crate::errors::{MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::generated as proto; -// MEMPOOL CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum MempoolConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Block(#[from] BlockConversionError), - #[error(transparent)] - Transaction(#[from] TransactionConversionError), - #[error(transparent)] - Note(#[from] NoteConversionError), - #[error(transparent)] - Digest(#[from] DigestConversionError), -} - -impl From for tonic::Status { - fn from(value: MempoolConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - #[derive(Debug, Clone)] pub enum MempoolEvent { TransactionAdded { @@ -107,41 +79,34 @@ impl From for proto::block_producer::MempoolEvent { } impl TryFrom for MempoolEvent { - type Error = MempoolConversionError; + type Error = ConversionError; fn try_from(event: proto::block_producer::MempoolEvent) -> Result { - let event = - event.event.ok_or(proto::block_producer::MempoolEvent::missing_field("event"))?; + let event = event.event.ok_or(ConversionError::missing_field::< + proto::block_producer::MempoolEvent, + >("event"))?; match event { proto::block_producer::mempool_event::Event::TransactionAdded(tx) => { let id = tx .id - .ok_or(proto::block_producer::mempool_event::TransactionAdded::missing_field( - "id", - ))? - .try_into() - .map_err(MempoolConversionError::from)?; - let nullifiers = tx - .nullifiers - .into_iter() - .map(|n| Nullifier::try_from(n).map_err(MempoolConversionError::from)) - .collect::>()?; + .ok_or(ConversionError::missing_field::< + proto::block_producer::mempool_event::TransactionAdded, + >("id"))? + .try_into()?; + let nullifiers = + tx.nullifiers.into_iter().map(Nullifier::try_from).collect::>()?; let network_notes = tx .network_notes .into_iter() - .map(|n| { - AccountTargetNetworkNote::try_from(n).map_err(MempoolConversionError::from) - }) + .map(AccountTargetNetworkNote::try_from) .collect::>()?; let account_delta = tx .network_account_delta .as_deref() .map(AccountUpdateDetails::read_from_bytes) .transpose() - .map_err(|err| { - ProtoConversionError::deserialization_error("account_delta", err) - })?; + .map_err(|err| ConversionError::deserialization("account_delta", err))?; Ok(Self::TransactionAdded { id, @@ -153,15 +118,15 @@ impl TryFrom for MempoolEvent { proto::block_producer::mempool_event::Event::BlockCommitted(block_committed) => { let header = block_committed .block_header - .ok_or(proto::block_producer::mempool_event::BlockCommitted::missing_field( - "block_header", - ))? + .ok_or(ConversionError::missing_field::< + proto::block_producer::mempool_event::BlockCommitted, + >("block_header"))? .try_into()?; let header = Box::new(header); let txs = block_committed .transactions .into_iter() - .map(|t| TransactionId::try_from(t).map_err(MempoolConversionError::from)) + .map(TransactionId::try_from) .collect::>()?; Ok(Self::BlockCommitted { header, txs }) @@ -170,7 +135,7 @@ impl TryFrom for MempoolEvent { let txs = txs .reverted .into_iter() - .map(|t| TransactionId::try_from(t).map_err(MempoolConversionError::from)) + .map(TransactionId::try_from) .collect::>()?; Ok(Self::TransactionsReverted(txs)) diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index a3219c7a71..4379e266a2 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -1,43 +1,12 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::mmr::{Forest, MmrDelta}; -use miden_protocol::crypto::merkle::smt::{ - LeafIndex, - SmtLeaf, - SmtLeafError, - SmtProof, - SmtProofError, -}; +use miden_protocol::crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath}; -use thiserror::Error; -use crate::domain::digest::DigestConversionError; use crate::domain::{convert, try_convert}; -use crate::errors::{MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::generated as proto; -// MERKLE CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum MerkleConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Digest(#[from] DigestConversionError), - #[error("merkle error")] - MerkleError(#[from] miden_protocol::crypto::merkle::MerkleError), - #[error("SMT leaf error")] - SmtLeafError(#[from] SmtLeafError), - #[error("SMT proof error")] - SmtProofError(#[from] SmtProofError), -} - -impl From for tonic::Status { - fn from(value: MerkleConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - // MERKLE PATH // ================================================================================================ @@ -55,19 +24,15 @@ impl From for proto::primitives::MerklePath { } impl TryFrom<&proto::primitives::MerklePath> for MerklePath { - type Error = MerkleConversionError; + type Error = ConversionError; fn try_from(merkle_path: &proto::primitives::MerklePath) -> Result { - merkle_path - .siblings - .iter() - .map(|s| Word::try_from(s).map_err(MerkleConversionError::from)) - .collect() + merkle_path.siblings.iter().map(Word::try_from).collect() } } impl TryFrom for MerklePath { - type Error = MerkleConversionError; + type Error = ConversionError; fn try_from(merkle_path: proto::primitives::MerklePath) -> Result { (&merkle_path).try_into() @@ -88,7 +53,7 @@ impl From for proto::primitives::SparseMerklePath { } impl TryFrom for SparseMerklePath { - type Error = MerkleConversionError; + type Error = ConversionError; fn try_from(merkle_path: proto::primitives::SparseMerklePath) -> Result { Ok(SparseMerklePath::from_parts( @@ -96,7 +61,7 @@ impl TryFrom for SparseMerklePath { merkle_path .siblings .into_iter() - .map(|s| Word::try_from(s).map_err(MerkleConversionError::from)) + .map(Word::try_from) .collect::, _>>()?, )?) } @@ -116,14 +81,11 @@ impl From for proto::primitives::MmrDelta { } impl TryFrom for MmrDelta { - type Error = MerkleConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::MmrDelta) -> Result { - let data: Result, MerkleConversionError> = value - .data - .into_iter() - .map(|d| Word::try_from(d).map_err(MerkleConversionError::from)) - .collect(); + let data: Result, ConversionError> = + value.data.into_iter().map(Word::try_from).collect(); Ok(MmrDelta { forest: Forest::new(value.forest as usize), @@ -139,10 +101,12 @@ impl TryFrom for MmrDelta { // ------------------------------------------------------------------------------------------------ impl TryFrom for SmtLeaf { - type Error = MerkleConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::SmtLeaf) -> Result { - let leaf = value.leaf.ok_or(proto::primitives::SmtLeaf::missing_field(stringify!(leaf)))?; + let leaf = value.leaf.ok_or( + ConversionError::missing_field::(stringify!(leaf)), + )?; match leaf { proto::primitives::smt_leaf::Leaf::EmptyLeafIndex(leaf_index) => { @@ -183,16 +147,20 @@ impl From for proto::primitives::SmtLeaf { // ------------------------------------------------------------------------------------------------ impl TryFrom for (Word, Word) { - type Error = MerkleConversionError; + type Error = ConversionError; fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { let key: Word = entry .key - .ok_or(proto::primitives::SmtLeafEntry::missing_field(stringify!(key)))? + .ok_or(ConversionError::missing_field::(stringify!( + key + )))? .try_into()?; let value: Word = entry .value - .ok_or(proto::primitives::SmtLeafEntry::missing_field(stringify!(value)))? + .ok_or(ConversionError::missing_field::(stringify!( + value + )))? .try_into()?; Ok((key, value)) @@ -212,16 +180,20 @@ impl From<(Word, Word)> for proto::primitives::SmtLeafEntry { // ------------------------------------------------------------------------------------------------ impl TryFrom for SmtProof { - type Error = MerkleConversionError; + type Error = ConversionError; fn try_from(opening: proto::primitives::SmtOpening) -> Result { let path: SparseMerklePath = opening .path - .ok_or(proto::primitives::SmtOpening::missing_field(stringify!(path)))? + .ok_or(ConversionError::missing_field::(stringify!( + path + )))? .try_into()?; let leaf: SmtLeaf = opening .leaf - .ok_or(proto::primitives::SmtOpening::missing_field(stringify!(leaf)))? + .ok_or(ConversionError::missing_field::(stringify!( + leaf + )))? .try_into()?; Ok(SmtProof::new(path, leaf)?) diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 82f52ffc43..a1eda1603a 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -1,4 +1,3 @@ -use std::num::TryFromIntError; use std::sync::Arc; use miden_protocol::crypto::merkle::SparseMerklePath; @@ -16,44 +15,11 @@ use miden_protocol::note::{ }; use miden_protocol::utils::{Deserializable, Serializable}; use miden_protocol::{MastForest, MastNodeId, Word}; -use miden_standards::note::{AccountTargetNetworkNote, NetworkAccountTargetError}; -use thiserror::Error; +use miden_standards::note::AccountTargetNetworkNote; -use crate::domain::account::AccountConversionError; -use crate::domain::digest::DigestConversionError; -use crate::domain::merkle::MerkleConversionError; -use crate::errors::{ConversionError, MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::generated as proto; -// NOTE CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum NoteConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Digest(#[from] DigestConversionError), - #[error(transparent)] - Merkle(#[from] MerkleConversionError), - #[error(transparent)] - Account(#[from] AccountConversionError), - #[error("note error")] - NoteError(#[from] miden_protocol::errors::NoteError), - #[error("network note error")] - NetworkNoteError(#[source] NetworkAccountTargetError), - #[error("enum variant discriminant out of range")] - EnumDiscriminantOutOfRange, - #[error("integer conversion error: {0}")] - TryFromIntError(#[from] TryFromIntError), -} - -impl From for tonic::Status { - fn from(value: NoteConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - // NOTE TYPE // ================================================================================================ @@ -67,14 +33,14 @@ impl From for proto::note::NoteType { } impl TryFrom for NoteType { - type Error = NoteConversionError; + type Error = ConversionError; fn try_from(note_type: proto::note::NoteType) -> Result { match note_type { proto::note::NoteType::Public => Ok(NoteType::Public), proto::note::NoteType::Private => Ok(NoteType::Private), proto::note::NoteType::Unspecified => { - Err(NoteConversionError::EnumDiscriminantOutOfRange) + Err(ConversionError::message("enum variant discriminant out of range")) }, } } @@ -84,16 +50,17 @@ impl TryFrom for NoteType { // ================================================================================================ impl TryFrom for NoteMetadata { - type Error = NoteConversionError; + type Error = ConversionError; fn try_from(value: proto::note::NoteMetadata) -> Result { let sender = value .sender - .ok_or_else(|| proto::note::NoteMetadata::missing_field(stringify!(sender)))? - .try_into() - .map_err(NoteConversionError::from)?; + .ok_or_else(|| { + ConversionError::missing_field::(stringify!(sender)) + })? + .try_into()?; let note_type = proto::note::NoteType::try_from(value.note_type) - .map_err(|_| NoteConversionError::EnumDiscriminantOutOfRange)? + .map_err(|_| ConversionError::message("enum variant discriminant out of range"))? .try_into()?; let tag = NoteTag::new(value.tag); @@ -102,7 +69,7 @@ impl TryFrom for NoteMetadata { NoteAttachment::default() } else { NoteAttachment::read_from_bytes(&value.attachment) - .map_err(|err| ProtoConversionError::deserialization_error("NoteAttachment", err))? + .map_err(|err| ConversionError::deserialization("NoteAttachment", err))? }; Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment)) @@ -138,18 +105,20 @@ impl From for proto::note::NetworkNote { } impl TryFrom for AccountTargetNetworkNote { - type Error = NoteConversionError; + type Error = ConversionError; fn try_from(value: proto::note::NetworkNote) -> Result { let details = NoteDetails::read_from_bytes(&value.details) - .map_err(|err| ProtoConversionError::deserialization_error("NoteDetails", err))?; + .map_err(|err| ConversionError::deserialization("NoteDetails", err))?; let (assets, recipient) = details.into_parts(); let metadata: NoteMetadata = value .metadata - .ok_or_else(|| proto::note::NetworkNote::missing_field(stringify!(metadata)))? + .ok_or_else(|| { + ConversionError::missing_field::(stringify!(metadata)) + })? .try_into()?; let note = Note::new(assets, metadata, recipient); - AccountTargetNetworkNote::new(note).map_err(NoteConversionError::NetworkNoteError) + AccountTargetNetworkNote::new(note).map_err(ConversionError::from) } } @@ -171,13 +140,13 @@ impl From for proto::note::NoteId { } impl TryFrom for Word { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(note_id: proto::note::NoteId) -> Result { note_id .id .as_ref() - .ok_or(proto::note::NoteId::missing_field(stringify!(id)))? + .ok_or(ConversionError::missing_field::(stringify!(id)))? .try_into() } } @@ -200,7 +169,7 @@ impl From<(&NoteId, &NoteInclusionProof)> for proto::note::NoteInclusionInBlockP } impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusionProof) { - type Error = NoteConversionError; + type Error = ConversionError; fn try_from( proof: &proto::note::NoteInclusionInBlockProof, @@ -209,9 +178,9 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion proof .inclusion_path .as_ref() - .ok_or(proto::note::NoteInclusionInBlockProof::missing_field(stringify!( - inclusion_path - )))? + .ok_or(ConversionError::missing_field::( + stringify!(inclusion_path), + ))? .clone(), )?; @@ -219,10 +188,12 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion proof .note_id .as_ref() - .ok_or(proto::note::NoteInclusionInBlockProof::missing_field(stringify!(note_id)))? + .ok_or(ConversionError::missing_field::( + stringify!(note_id), + ))? .id .as_ref() - .ok_or(proto::note::NoteId::missing_field(stringify!(id)))?, + .ok_or(ConversionError::missing_field::(stringify!(id)))?, )?; Ok(( @@ -237,20 +208,20 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion } impl TryFrom for Note { - type Error = NoteConversionError; + type Error = ConversionError; fn try_from(proto_note: proto::note::Note) -> Result { let metadata: NoteMetadata = proto_note .metadata - .ok_or(proto::note::Note::missing_field(stringify!(metadata)))? + .ok_or(ConversionError::missing_field::(stringify!(metadata)))? .try_into()?; let details = proto_note .details - .ok_or(proto::note::Note::missing_field(stringify!(details)))?; + .ok_or(ConversionError::missing_field::(stringify!(details)))?; let note_details = NoteDetails::read_from_bytes(&details) - .map_err(|err| ProtoConversionError::deserialization_error("NoteDetails", err))?; + .map_err(|err| ConversionError::deserialization("NoteDetails", err))?; let (assets, recipient) = note_details.into_parts(); Ok(Note::new(assets, metadata, recipient)) @@ -275,11 +246,15 @@ impl TryFrom for NoteHeader { fn try_from(value: proto::note::NoteHeader) -> Result { let note_id_word: Word = value .note_id - .ok_or_else(|| proto::note::NoteHeader::missing_field(stringify!(note_id)))? + .ok_or_else(|| { + ConversionError::missing_field::(stringify!(note_id)) + })? .try_into()?; let metadata: NoteMetadata = value .metadata - .ok_or_else(|| proto::note::NoteHeader::missing_field(stringify!(metadata)))? + .ok_or_else(|| { + ConversionError::missing_field::(stringify!(metadata)) + })? .try_into()?; Ok(NoteHeader::new(NoteId::from_raw(note_id_word), metadata)) @@ -299,16 +274,15 @@ impl From for proto::note::NoteScript { } impl TryFrom for NoteScript { - type Error = NoteConversionError; + type Error = ConversionError; fn try_from(value: proto::note::NoteScript) -> Result { let proto::note::NoteScript { entrypoint, mast } = value; let mast = MastForest::read_from_bytes(&mast) - .map_err(|err| ProtoConversionError::deserialization_error("note_script.mast", err))?; - let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast).map_err(|err| { - ProtoConversionError::deserialization_error("note_script.entrypoint", err) - })?; + .map_err(|err| ConversionError::deserialization("note_script.mast", err))?; + let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast) + .map_err(|err| ConversionError::deserialization("note_script.entrypoint", err))?; Ok(Self::from_parts(Arc::new(mast), entrypoint)) } diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index c259f0063d..319137f49c 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -1,32 +1,10 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::smt::SmtProof; use miden_protocol::note::Nullifier; -use thiserror::Error; -use crate::domain::digest::DigestConversionError; -use crate::domain::merkle::MerkleConversionError; -use crate::errors::{MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::generated as proto; -// NULLIFIER CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum NullifierConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Digest(#[from] DigestConversionError), - #[error(transparent)] - Merkle(#[from] MerkleConversionError), -} - -impl From for tonic::Status { - fn from(value: NullifierConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - // FROM NULLIFIER // ================================================================================================ @@ -46,7 +24,7 @@ impl From for proto::primitives::Digest { // ================================================================================================ impl TryFrom for Nullifier { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::Digest) -> Result { let digest: Word = value.try_into()?; @@ -64,7 +42,7 @@ pub struct NullifierWitnessRecord { } impl TryFrom for NullifierWitnessRecord { - type Error = NullifierConversionError; + type Error = ConversionError; fn try_from( nullifier_witness_record: proto::store::block_inputs::NullifierWitness, @@ -72,16 +50,15 @@ impl TryFrom for NullifierWitnessR Ok(Self { nullifier: nullifier_witness_record .nullifier - .ok_or(proto::store::block_inputs::NullifierWitness::missing_field(stringify!( - nullifier - )))? - .try_into() - .map_err(NullifierConversionError::from)?, + .ok_or(ConversionError::missing_field::< + proto::store::block_inputs::NullifierWitness, + >(stringify!(nullifier)))? + .try_into()?, proof: nullifier_witness_record .opening - .ok_or(proto::store::block_inputs::NullifierWitness::missing_field(stringify!( - opening - )))? + .ok_or(ConversionError::missing_field::< + proto::store::block_inputs::NullifierWitness, + >(stringify!(opening)))? .try_into()?, }) } diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index 83f4852e04..5b057281fe 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -2,29 +2,10 @@ use miden_protocol::Word; use miden_protocol::note::Nullifier; use miden_protocol::transaction::{InputNoteCommitment, TransactionId}; use miden_protocol::utils::{Deserializable, Serializable}; -use thiserror::Error; -use crate::domain::digest::DigestConversionError; -use crate::errors::{ConversionError, MissingFieldHelper, ProtoConversionError}; +use crate::errors::ConversionError; use crate::generated as proto; -// TRANSACTION CONVERSION ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum TransactionConversionError { - #[error(transparent)] - Proto(#[from] ProtoConversionError), - #[error(transparent)] - Digest(#[from] DigestConversionError), -} - -impl From for tonic::Status { - fn from(value: TransactionConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) - } -} - // FROM TRANSACTION ID // ================================================================================================ @@ -56,7 +37,7 @@ impl From for proto::transaction::TransactionId { // ================================================================================================ impl TryFrom for TransactionId { - type Error = DigestConversionError; + type Error = ConversionError; fn try_from(value: proto::primitives::Digest) -> Result { let digest: Word = value.try_into()?; @@ -65,14 +46,13 @@ impl TryFrom for TransactionId { } impl TryFrom for TransactionId { - type Error = TransactionConversionError; + type Error = ConversionError; fn try_from(value: proto::transaction::TransactionId) -> Result { value .id - .ok_or(proto::transaction::TransactionId::missing_field("id"))? + .ok_or(ConversionError::missing_field::("id"))? .try_into() - .map_err(TransactionConversionError::from) } } @@ -95,7 +75,9 @@ impl TryFrom for InputNoteCommitment { let nullifier: Nullifier = value .nullifier .ok_or_else(|| { - proto::transaction::InputNoteCommitment::missing_field(stringify!(nullifier)) + ConversionError::missing_field::( + stringify!(nullifier), + ) })? .try_into()?; @@ -109,6 +91,6 @@ impl TryFrom for InputNoteCommitment { nullifier.write_into(&mut bytes); header.write_into(&mut bytes); InputNoteCommitment::read_from_bytes(&bytes) - .map_err(|err| ConversionError::deserialization_error("InputNoteCommitment", err)) + .map_err(|err| ConversionError::deserialization("InputNoteCommitment", err)) } } diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index a68546030f..5f4788d433 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -1,104 +1,223 @@ use std::any::type_name; +use std::fmt; // Re-export the GrpcError derive macro for convenience pub use miden_node_grpc_error_macro::GrpcError; use miden_protocol::utils::DeserializationError; -use thiserror::Error; - -// Re-export per-domain conversion errors -pub use crate::domain::account::AccountConversionError; -pub use crate::domain::batch::BatchConversionError; -pub use crate::domain::block::BlockConversionError; -pub use crate::domain::digest::DigestConversionError; -pub use crate::domain::mempool::MempoolConversionError; -pub use crate::domain::merkle::MerkleConversionError; -pub use crate::domain::note::NoteConversionError; -pub use crate::domain::nullifier::NullifierConversionError; -pub use crate::domain::transaction::TransactionConversionError; #[cfg(test)] mod test_macro; -// SHARED PROTO CONVERSION ERROR +// CONVERSION ERROR // ================================================================================================ -/// Shared error variants common to all protobuf conversions. -#[derive(Debug, Error)] -pub enum ProtoConversionError { - #[error("field `{entity}::{field_name}` is missing")] - MissingField { - entity: &'static str, - field_name: &'static str, - }, - #[error("failed to deserialize {entity}")] - DeserializationError { - entity: &'static str, - source: DeserializationError, - }, +/// Opaque error for protobuf-to-domain conversions. +/// +/// Captures an underlying error plus an optional field path stack that describes which nested +/// field caused the error (e.g. `"block.header.account_root: value is not in range 0..MODULUS"`). +/// +/// Always maps to [`tonic::Status::invalid_argument()`]. +#[derive(Debug)] +pub struct ConversionError { + path: Vec<&'static str>, + source: Box, +} + +impl ConversionError { + /// Create a new `ConversionError` wrapping any error source. + pub fn new(source: impl std::error::Error + Send + Sync + 'static) -> Self { + Self { + path: Vec::new(), + source: Box::new(source), + } + } + + /// Add field context to the error path. + /// + /// Called from inner to outer, so the path accumulates in reverse + /// (outermost field pushed last). + #[must_use] + pub fn context(mut self, field: &'static str) -> Self { + self.path.push(field); + self + } + + /// Create a "missing field" error for a protobuf message type `T`. + pub fn missing_field(field_name: &'static str) -> Self { + Self { + path: Vec::new(), + source: Box::new(MissingFieldError { entity: type_name::(), field_name }), + } + } + + /// Create a deserialization error for a named entity. + pub fn deserialization(entity: &'static str, source: DeserializationError) -> Self { + Self { + path: Vec::new(), + source: Box::new(DeserializationErrorWrapper { entity, source }), + } + } + + /// Create a `ConversionError` from an ad-hoc error message. + pub fn message(msg: impl Into) -> Self { + Self { + path: Vec::new(), + source: Box::new(StringError(msg.into())), + } + } } -impl ProtoConversionError { - pub fn deserialization_error(entity: &'static str, source: DeserializationError) -> Self { - Self::DeserializationError { entity, source } +impl fmt::Display for ConversionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.path.is_empty() { + // Path was pushed inner-to-outer, so reverse for display. + for (i, segment) in self.path.iter().rev().enumerate() { + if i > 0 { + f.write_str(".")?; + } + f.write_str(segment)?; + } + f.write_str(": ")?; + } + write!(f, "{}", self.source) } } -impl From for tonic::Status { - fn from(value: ProtoConversionError) -> Self { +impl std::error::Error for ConversionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&*self.source) + } +} + +impl From for tonic::Status { + fn from(value: ConversionError) -> Self { tonic::Status::invalid_argument(value.to_string()) } } -pub trait MissingFieldHelper { - fn missing_field(field_name: &'static str) -> ProtoConversionError; +// INTERNAL HELPER ERROR TYPES +// ================================================================================================ + +#[derive(Debug)] +struct MissingFieldError { + entity: &'static str, + field_name: &'static str, +} + +impl fmt::Display for MissingFieldError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "field `{}::{}` is missing", self.entity, self.field_name) + } +} + +impl std::error::Error for MissingFieldError {} + +#[derive(Debug)] +struct DeserializationErrorWrapper { + entity: &'static str, + source: DeserializationError, +} + +impl fmt::Display for DeserializationErrorWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "failed to deserialize {}: {}", self.entity, self.source) + } +} + +impl std::error::Error for DeserializationErrorWrapper { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.source) + } } -impl MissingFieldHelper for T { - fn missing_field(field_name: &'static str) -> ProtoConversionError { - ProtoConversionError::MissingField { entity: type_name::(), field_name } +#[derive(Debug)] +struct StringError(String); + +impl fmt::Display for StringError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) } } -// CONVERSION ERROR (WRAPPER) +impl std::error::Error for StringError {} + +// FROM IMPLS FOR EXTERNAL ERROR TYPES // ================================================================================================ -/// Union error type that wraps all per-domain conversion errors. -/// -/// This preserves backward compatibility for downstream crates that use `#[from] ConversionError`. -/// Prefer using the domain-specific error types (e.g. `DigestConversionError`, -/// `AccountConversionError`) at conversion boundaries. -#[derive(Debug, Error)] -pub enum ConversionError { - #[error(transparent)] - Digest(#[from] DigestConversionError), - #[error(transparent)] - Account(#[from] AccountConversionError), - #[error(transparent)] - Note(#[from] NoteConversionError), - #[error(transparent)] - Block(#[from] BlockConversionError), - #[error(transparent)] - Merkle(#[from] MerkleConversionError), - #[error(transparent)] - Nullifier(#[from] NullifierConversionError), - #[error(transparent)] - Transaction(#[from] TransactionConversionError), - #[error(transparent)] - Batch(#[from] BatchConversionError), - #[error(transparent)] - Mempool(#[from] MempoolConversionError), - #[error(transparent)] - Proto(#[from] ProtoConversionError), +impl From for ConversionError { + fn from(e: hex::FromHexError) -> Self { + Self::new(e) + } } -impl ConversionError { - pub fn deserialization_error(entity: &'static str, source: DeserializationError) -> Self { - Self::Proto(ProtoConversionError::deserialization_error(entity, source)) +impl From for ConversionError { + fn from(e: miden_protocol::errors::AccountError) -> Self { + Self::new(e) } } -impl From for tonic::Status { - fn from(value: ConversionError) -> Self { - tonic::Status::invalid_argument(value.to_string()) +impl From for ConversionError { + fn from(e: miden_protocol::errors::AssetError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_protocol::errors::FeeError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_protocol::errors::NoteError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_protocol::errors::StorageSlotNameError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_protocol::crypto::merkle::MerkleError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_protocol::crypto::merkle::smt::SmtLeafError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_protocol::crypto::merkle::smt::SmtProofError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: std::num::TryFromIntError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_standards::note::NetworkAccountTargetError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: DeserializationError) -> Self { + Self::new(e) + } +} + +impl From for ConversionError { + fn from(e: miden_protocol::errors::AssetVaultError) -> Self { + Self::new(e) } } diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index 256600b1bb..1f887175d6 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -3,7 +3,7 @@ use std::time::Duration; use anyhow::Context; use miden_node_proto::clients::{BlockProducerClient, Builder, StoreRpcClient, ValidatorClient}; -use miden_node_proto::errors::DigestConversionError; +use miden_node_proto::errors::ConversionError; use miden_node_proto::generated::rpc::MempoolStats; use miden_node_proto::generated::rpc::api_server::{self, Api}; use miden_node_proto::generated::{self as proto}; @@ -243,11 +243,12 @@ impl api_server::Api for RpcService { // Validation checking for correct NoteId's let note_ids = request.get_ref().ids.clone(); - let _: Vec = try_convert(note_ids).collect::>().map_err( - |err: DigestConversionError| { - Status::invalid_argument(err.as_report_context("invalid NoteId")) - }, - )?; + let _: Vec = + try_convert(note_ids) + .collect::>() + .map_err(|err: ConversionError| { + Status::invalid_argument(err.as_report_context("invalid NoteId")) + })?; self.store.clone().get_notes_by_id(request).await } diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index b73748cb86..543ab1257e 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -1,12 +1,7 @@ use std::collections::BTreeSet; use std::sync::Arc; -use miden_node_proto::errors::{ - AccountConversionError, - ConversionError, - DigestConversionError, - ProtoConversionError, -}; +use miden_node_proto::errors::ConversionError; use miden_node_proto::generated as proto; use miden_node_utils::ErrorReport; use miden_protocol::Word; @@ -114,11 +109,7 @@ where E: From, { block_range.ok_or_else(|| { - ConversionError::from(ProtoConversionError::MissingField { - entity, - field_name: "block_range", - }) - .into() + ConversionError::message(format!("{entity}: missing field `block_range`")).into() }) } @@ -131,11 +122,9 @@ pub fn read_root( where E: From, { - root.ok_or_else(|| { - ConversionError::from(ProtoConversionError::MissingField { entity, field_name: "root" }) - })? - .try_into() - .map_err(|e: DigestConversionError| ConversionError::from(e).into()) + root.ok_or_else(|| ConversionError::message(format!("{entity}: missing field `root`")))? + .try_into() + .map_err(|e: ConversionError| e.into()) } /// Converts a collection of proto primitives to Words, returning a specific error type if @@ -144,13 +133,13 @@ pub fn convert_digests_to_words(digests: I) -> Result, E> where E: From, I: IntoIterator, - I::Item: TryInto, + I::Item: TryInto, { digests .into_iter() .map(TryInto::try_into) - .collect::, DigestConversionError>>() - .map_err(|e| ConversionError::from(e).into()) + .collect::, ConversionError>>() + .map_err(Into::into) } /// Reads account IDs from a request, returning a specific error type if conversion fails @@ -162,24 +151,17 @@ where .iter() .cloned() .map(AccountId::try_from) - .collect::>() - .map_err(|e| ConversionError::from(e).into()) + .collect::>() + .map_err(Into::into) } pub fn read_account_id(id: Option) -> Result where E: From, { - id.ok_or_else(|| { - ConversionError::from(ProtoConversionError::deserialization_error( - "AccountId", - miden_protocol::crypto::utils::DeserializationError::InvalidValue( - "Missing account ID".to_string(), - ), - )) - })? - .try_into() - .map_err(|e: AccountConversionError| ConversionError::from(e).into()) + id.ok_or_else(|| ConversionError::message("missing account ID"))? + .try_into() + .map_err(|e: ConversionError| e.into()) } #[instrument( @@ -196,7 +178,7 @@ where nullifiers .iter() .copied() - .map(|d| Nullifier::try_from(d).map_err(ConversionError::from)) + .map(Nullifier::try_from) .collect::>() .map_err(Into::into) } diff --git a/crates/store/src/server/block_producer.rs b/crates/store/src/server/block_producer.rs index 25f6b05f60..e3188190b8 100644 --- a/crates/store/src/server/block_producer.rs +++ b/crates/store/src/server/block_producer.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; use futures::TryFutureExt; use miden_crypto::dsa::ecdsa_k256_keccak::Signature; -use miden_node_proto::errors::MissingFieldHelper; +use miden_node_proto::errors::ConversionError; use miden_node_proto::generated::store::block_producer_server; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -55,23 +55,29 @@ impl block_producer_server::BlockProducer for StoreApi { ) })?; // Read block. - let block = request - .block - .ok_or(proto::store::ApplyBlockRequest::missing_field(stringify!(block)))?; + let block = request.block.ok_or(ConversionError::missing_field::< + proto::store::ApplyBlockRequest, + >(stringify!(block)))?; // Read block header. let header: BlockHeader = block .header - .ok_or(proto::blockchain::SignedBlock::missing_field(stringify!(header)))? + .ok_or(ConversionError::missing_field::(stringify!( + header + )))? .try_into()?; // Read block body. let body: BlockBody = block .body - .ok_or(proto::blockchain::SignedBlock::missing_field(stringify!(body)))? + .ok_or(ConversionError::missing_field::(stringify!( + body + )))? .try_into()?; // Read signature. let signature: Signature = block .signature - .ok_or(proto::blockchain::SignedBlock::missing_field(stringify!(signature)))? + .ok_or(ConversionError::missing_field::(stringify!( + signature + )))? .try_into()?; // Get block inputs from ordered batches. diff --git a/crates/store/src/server/ntx_builder.rs b/crates/store/src/server/ntx_builder.rs index 3508b893ad..f6e8d4a7a5 100644 --- a/crates/store/src/server/ntx_builder.rs +++ b/crates/store/src/server/ntx_builder.rs @@ -3,7 +3,6 @@ use std::num::{NonZero, TryFromIntError}; use miden_crypto::merkle::smt::SmtProof; use miden_node_proto::domain::account::AccountInfo; -use miden_node_proto::errors::ConversionError; use miden_node_proto::generated as proto; use miden_node_proto::generated::rpc::BlockRange; use miden_node_proto::generated::store::ntx_builder_server; @@ -173,9 +172,7 @@ impl ntx_builder_server::NtxBuilder for StoreApi { ) -> Result, Status> { debug!(target: COMPONENT, ?request); let request = request.into_inner(); - let account_request = request - .try_into() - .map_err(|e| GetAccountError::DeserializationFailed(ConversionError::from(e)))?; + let account_request = request.try_into().map_err(GetAccountError::DeserializationFailed)?; let proof = self.state.get_account(account_request).await?; diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index daeea6f28d..4b8d6d6780 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -1,6 +1,6 @@ use miden_node_proto::convert; use miden_node_proto::domain::block::InvalidBlockRange; -use miden_node_proto::errors::{ConversionError, MissingFieldHelper}; +use miden_node_proto::errors::ConversionError; use miden_node_proto::generated::store::rpc_server; use miden_node_proto::generated::{self as proto}; use miden_node_utils::limiter::{ @@ -163,8 +163,12 @@ impl rpc_server::Rpc for StoreApi { let block_range = request .block_range - .ok_or_else(|| proto::rpc::SyncChainMmrRequest::missing_field(stringify!(block_range))) - .map_err(|e| SyncChainMmrError::DeserializationFailed(ConversionError::from(e)))?; + .ok_or_else(|| { + ConversionError::missing_field::(stringify!( + block_range + )) + }) + .map_err(SyncChainMmrError::DeserializationFailed)?; let block_from = BlockNumber::from(block_range.block_from); if block_from > chain_tip { @@ -246,9 +250,7 @@ impl rpc_server::Rpc for StoreApi { ) -> Result, Status> { debug!(target: COMPONENT, ?request); let request = request.into_inner(); - let account_request = request - .try_into() - .map_err(|e| GetAccountError::DeserializationFailed(ConversionError::from(e)))?; + let account_request = request.try_into().map_err(GetAccountError::DeserializationFailed)?; let account_data = self.state.get_account(account_request).await?; From 3a07273130f5deb9dc4dbf258200cc8c993e30f9 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 13:44:36 +1300 Subject: [PATCH 03/26] Add ctx ext trait --- crates/proto/src/domain/account.rs | 111 ++++++++++++++++--------- crates/proto/src/domain/batch.rs | 8 +- crates/proto/src/domain/block.rs | 60 ++++++++----- crates/proto/src/domain/mempool.rs | 25 ++++-- crates/proto/src/domain/merkle.rs | 31 ++++--- crates/proto/src/domain/note.rs | 28 ++++--- crates/proto/src/domain/nullifier.rs | 8 +- crates/proto/src/domain/transaction.rs | 8 +- crates/proto/src/errors/mod.rs | 27 ++++++ 9 files changed, 208 insertions(+), 98 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 25ed44ea2d..eac0e55977 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -25,7 +25,7 @@ use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError}; use thiserror::Error; use super::try_convert; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::generated::{self as proto}; #[cfg(test)] @@ -129,10 +129,12 @@ impl TryFrom for AccountStorageHeader { let commitment = slot .commitment .ok_or(ConversionError::message("value is not in the range 0..MODULUS"))? - .try_into()?; + .try_into() + .context("commitment")?; Ok(StorageSlotHeader::new(slot_name, slot_type, commitment)) }) - .collect::, ConversionError>>()?; + .collect::, ConversionError>>() + .context("slots")?; Ok(AccountStorageHeader::new(slot_headers)?) } @@ -159,10 +161,11 @@ impl TryFrom for AccountRequest { .ok_or(ConversionError::missing_field::(stringify!( account_id )))? - .try_into()?; + .try_into() + .context("account_id")?; let block_num = block_num.map(Into::into); - let details = details.map(TryFrom::try_from).transpose()?; + let details = details.map(TryFrom::try_from).transpose().context("details")?; Ok(AccountRequest { account_id, block_num, details }) } @@ -187,9 +190,14 @@ impl TryFrom for AccountDetai storage_maps, } = value; - let code_commitment = code_commitment.map(TryFrom::try_from).transpose()?; - let asset_vault_commitment = asset_vault_commitment.map(TryFrom::try_from).transpose()?; - let storage_requests = try_convert(storage_maps).collect::>()?; + let code_commitment = + code_commitment.map(TryFrom::try_from).transpose().context("code_commitment")?; + let asset_vault_commitment = asset_vault_commitment + .map(TryFrom::try_from) + .transpose() + .context("asset_vault_commitment")?; + let storage_requests = + try_convert(storage_maps).collect::>().context("storage_maps")?; Ok(AccountDetailRequest { code_commitment, @@ -218,12 +226,13 @@ impl TryFrom(stringify!(slot_data)))? - .try_into()?; + .try_into() + .context("slot_data")?; Ok(StorageMapRequest { slot_name, slot_data }) } @@ -280,22 +289,26 @@ impl TryFrom for AccountHeader { .ok_or(ConversionError::missing_field::(stringify!( account_id )))? - .try_into()?; + .try_into() + .context("account_id")?; let vault_root = vault_root .ok_or(ConversionError::missing_field::(stringify!( vault_root )))? - .try_into()?; + .try_into() + .context("vault_root")?; let storage_commitment = storage_commitment .ok_or(ConversionError::missing_field::(stringify!( storage_commitment )))? - .try_into()?; + .try_into() + .context("storage_commitment")?; let code_commitment = code_commitment .ok_or(ConversionError::missing_field::(stringify!( code_commitment )))? - .try_into()?; + .try_into() + .context("code_commitment")?; let nonce = nonce .try_into() .map_err(|_e| ConversionError::message("value is not in the range 0..MODULUS"))?; @@ -397,9 +410,10 @@ impl TryFrom for AccountVaultDetails { let asset = asset.asset.ok_or(ConversionError::missing_field::< proto::primitives::Asset, >(stringify!(asset)))?; - let asset = Word::try_from(asset)?; + let asset = Word::try_from(asset).context("asset")?; Asset::try_from(asset).map_err(ConversionError::from) - }))?; + })) + .context("assets")?; Ok(Self::Assets(parsed_assets)) } } @@ -554,7 +568,7 @@ impl TryFrom entries, } = value; - let slot_name = StorageSlotName::new(slot_name)?; + let slot_name = StorageSlotName::new(slot_name).context("slot_name")?; let entries = if too_many_entries { StorageMapEntries::LimitExceeded @@ -575,16 +589,19 @@ impl TryFrom stringify!(key), ))? .try_into() - .map(StorageMapKey::new)?; + .map(StorageMapKey::new) + .context("key")?; let value = entry .value .ok_or(ConversionError::missing_field::( stringify!(value), ))? - .try_into()?; + .try_into() + .context("value")?; Ok((key, value)) }) - .collect::, ConversionError>>()?; + .collect::, ConversionError>>() + .context("entries")?; StorageMapEntries::AllEntries(entries) }, Some(ProtoEntries::EntriesWithProofs(MapEntriesWithProofs { entries })) => { @@ -597,9 +614,10 @@ impl TryFrom >(stringify!( proof )))?; - SmtProof::try_from(smt_opening) + SmtProof::try_from(smt_opening).context("proof") }) - .collect::, ConversionError>>()?; + .collect::, ConversionError>>() + .context("entries")?; StorageMapEntries::EntriesWithProofs(proofs) }, } @@ -701,9 +719,11 @@ impl TryFrom for AccountStorageDetails { .ok_or(ConversionError::missing_field::( stringify!(header), ))? - .try_into()?; + .try_into() + .context("header")?; - let map_details = try_convert(map_details).collect::, _>>()?; + let map_details = + try_convert(map_details).collect::, _>>().context("map_details")?; Ok(Self { header, map_details }) } @@ -761,9 +781,10 @@ impl TryFrom for AccountResponse { .ok_or(ConversionError::missing_field::(stringify!( witness )))? - .try_into()?; + .try_into() + .context("witness")?; - let details = details.map(TryFrom::try_from).transpose()?; + let details = details.map(TryFrom::try_from).transpose().context("details")?; Ok(AccountResponse { block_num, witness, details }) } @@ -825,19 +846,22 @@ impl TryFrom for AccountDetails { .ok_or(ConversionError::missing_field::( stringify!(header), ))? - .try_into()?; + .try_into() + .context("header")?; let storage_details = storage_details .ok_or(ConversionError::missing_field::( stringify!(storage_details), ))? - .try_into()?; + .try_into() + .context("storage_details")?; let vault_details = vault_details .ok_or(ConversionError::missing_field::( stringify!(vault_details), ))? - .try_into()?; + .try_into() + .context("vault_details")?; let account_code = code; Ok(AccountDetails { @@ -884,19 +908,22 @@ impl TryFrom for AccountWitness { .ok_or(ConversionError::missing_field::(stringify!( witness_id )))? - .try_into()?; + .try_into() + .context("witness_id")?; let commitment = account_witness .commitment .ok_or(ConversionError::missing_field::(stringify!( commitment )))? - .try_into()?; + .try_into() + .context("commitment")?; let path = account_witness .path .ok_or(ConversionError::missing_field::(stringify!( path )))? - .try_into()?; + .try_into() + .context("path")?; AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -938,13 +965,15 @@ impl TryFrom for AccountWitnessRecord { .ok_or(ConversionError::missing_field::(stringify!( witness_id )))? - .try_into()?; + .try_into() + .context("witness_id")?; let commitment = account_witness_record .commitment .ok_or(ConversionError::missing_field::(stringify!( commitment )))? - .try_into()?; + .try_into() + .context("commitment")?; let path: SparseMerklePath = account_witness_record .path .as_ref() @@ -952,7 +981,8 @@ impl TryFrom for AccountWitnessRecord { path )))? .clone() - .try_into()?; + .try_into() + .context("path")?; let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -967,7 +997,8 @@ impl TryFrom for AccountWitnessRecord { .ok_or(ConversionError::missing_field::( stringify!(account_id), ))? - .try_into()?, + .try_into() + .context("account_id")?, witness, }) } @@ -1017,14 +1048,16 @@ impl TryFrom fo .ok_or(ConversionError::missing_field::< proto::store::transaction_inputs::AccountTransactionInputRecord, >(stringify!(account_id)))? - .try_into()?; + .try_into() + .context("account_id")?; let account_commitment = from .account_commitment .ok_or(ConversionError::missing_field::< proto::store::transaction_inputs::AccountTransactionInputRecord, >(stringify!(account_commitment)))? - .try_into()?; + .try_into() + .context("account_commitment")?; // If the commitment is equal to `Word::empty()`, it signifies that this is a new // account which is not yet present in the Store. @@ -1057,7 +1090,7 @@ impl TryFrom for Asset { let inner = value .asset .ok_or(ConversionError::missing_field::("asset"))?; - let word = Word::try_from(inner)?; + let word = Word::try_from(inner).context("asset")?; Asset::try_from(word).map_err(ConversionError::from) } diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index 8e2acb9586..af30b8a157 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -5,7 +5,7 @@ use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::generated as proto; /// Data required for a transaction batch. @@ -34,12 +34,14 @@ impl TryFrom for BatchInputs { batch_reference_block_header: response .batch_reference_block_header .ok_or(ConversionError::missing_field::("block_header"))? - .try_into()?, + .try_into() + .context("batch_reference_block_header")?, note_proofs: response .note_proofs .iter() .map(<(NoteId, NoteInclusionProof)>::try_from) - .collect::>()?, + .collect::>() + .context("note_proofs")?, partial_block_chain: PartialBlockchain::read_from_bytes(&response.partial_block_chain) .map_err(|source| ConversionError::deserialization("PartialBlockchain", source))?, }; diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 7e6b981ebc..7d87adb3c2 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -17,7 +17,7 @@ use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; use thiserror::Error; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; // BLOCK NUMBER @@ -82,55 +82,64 @@ impl TryFrom for BlockHeader { .ok_or(ConversionError::missing_field::( stringify!(prev_block_commitment), ))? - .try_into()?, + .try_into() + .context("prev_block_commitment")?, value.block_num.into(), value .chain_commitment .ok_or(ConversionError::missing_field::( stringify!(chain_commitment), ))? - .try_into()?, + .try_into() + .context("chain_commitment")?, value .account_root .ok_or(ConversionError::missing_field::( stringify!(account_root), ))? - .try_into()?, + .try_into() + .context("account_root")?, value .nullifier_root .ok_or(ConversionError::missing_field::( stringify!(nullifier_root), ))? - .try_into()?, + .try_into() + .context("nullifier_root")?, value .note_root .ok_or(ConversionError::missing_field::( stringify!(note_root), ))? - .try_into()?, + .try_into() + .context("note_root")?, value .tx_commitment .ok_or(ConversionError::missing_field::( stringify!(tx_commitment), ))? - .try_into()?, + .try_into() + .context("tx_commitment")?, value .tx_kernel_commitment .ok_or(ConversionError::missing_field::( stringify!(tx_kernel_commitment), ))? - .try_into()?, + .try_into() + .context("tx_kernel_commitment")?, value .validator_key .ok_or(ConversionError::missing_field::( stringify!(validator_key), ))? - .try_into()?, + .try_into() + .context("validator_key")?, FeeParameters::try_from(value.fee_parameters.ok_or( ConversionError::missing_field::(stringify!( fee_parameters )), - )?)?, + )?) + .context("fee_parameters")?, value.timestamp, )) } @@ -202,19 +211,22 @@ impl TryFrom for SignedBlock { .ok_or(ConversionError::missing_field::(stringify!( header )))? - .try_into()?; + .try_into() + .context("header")?; let body = value .body .ok_or(ConversionError::missing_field::(stringify!( body )))? - .try_into()?; + .try_into() + .context("body")?; let signature = value .signature .ok_or(ConversionError::missing_field::(stringify!( signature )))? - .try_into()?; + .try_into() + .context("signature")?; Ok(SignedBlock::new_unchecked(header, body, signature)) } @@ -264,7 +276,8 @@ impl TryFrom for BlockInputs { .ok_or(ConversionError::missing_field::( "block_header", ))? - .try_into()?; + .try_into() + .context("latest_block_header")?; let account_witnesses = response .account_witnesses @@ -273,7 +286,8 @@ impl TryFrom for BlockInputs { let witness_record: AccountWitnessRecord = entry.try_into()?; Ok((witness_record.account_id, witness_record.witness)) }) - .collect::, ConversionError>>()?; + .collect::, ConversionError>>() + .context("account_witnesses")?; let nullifier_witnesses = response .nullifier_witnesses @@ -282,13 +296,15 @@ impl TryFrom for BlockInputs { let witness: NullifierWitnessRecord = entry.try_into()?; Ok((witness.nullifier, NullifierWitness::new(witness.proof))) }) - .collect::, ConversionError>>()?; + .collect::, ConversionError>>() + .context("nullifier_witnesses")?; let unauthenticated_note_proofs = response .unauthenticated_note_proofs .iter() .map(<(NoteId, NoteInclusionProof)>::try_from) - .collect::>()?; + .collect::>() + .context("unauthenticated_note_proofs")?; let partial_block_chain = PartialBlockchain::read_from_bytes(&response.partial_block_chain) .map_err(|source| ConversionError::deserialization("PartialBlockchain", source))?; @@ -355,11 +371,13 @@ impl From<&Signature> for proto::blockchain::BlockSignature { impl TryFrom for FeeParameters { type Error = ConversionError; fn try_from(fee_params: proto::blockchain::FeeParameters) -> Result { - let native_asset_id = fee_params.native_asset_id.map(AccountId::try_from).ok_or( - ConversionError::missing_field::(stringify!( + let native_asset_id = fee_params + .native_asset_id + .map(AccountId::try_from) + .ok_or(ConversionError::missing_field::(stringify!( native_asset_id - )), - )??; + )))? + .context("native_asset_id")?; let fee_params = FeeParameters::new(native_asset_id, fee_params.verification_base_fee)?; Ok(fee_params) } diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index ae39ec8dd5..32fdb5b7f8 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -7,7 +7,7 @@ use miden_protocol::transaction::TransactionId; use miden_protocol::utils::{Deserializable, Serializable}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::generated as proto; #[derive(Debug, Clone)] @@ -93,14 +93,20 @@ impl TryFrom for MempoolEvent { .ok_or(ConversionError::missing_field::< proto::block_producer::mempool_event::TransactionAdded, >("id"))? - .try_into()?; - let nullifiers = - tx.nullifiers.into_iter().map(Nullifier::try_from).collect::>()?; + .try_into() + .context("id")?; + let nullifiers = tx + .nullifiers + .into_iter() + .map(Nullifier::try_from) + .collect::>() + .context("nullifiers")?; let network_notes = tx .network_notes .into_iter() .map(AccountTargetNetworkNote::try_from) - .collect::>()?; + .collect::>() + .context("network_notes")?; let account_delta = tx .network_account_delta .as_deref() @@ -121,13 +127,15 @@ impl TryFrom for MempoolEvent { .ok_or(ConversionError::missing_field::< proto::block_producer::mempool_event::BlockCommitted, >("block_header"))? - .try_into()?; + .try_into() + .context("block_header")?; let header = Box::new(header); let txs = block_committed .transactions .into_iter() .map(TransactionId::try_from) - .collect::>()?; + .collect::>() + .context("transactions")?; Ok(Self::BlockCommitted { header, txs }) }, @@ -136,7 +144,8 @@ impl TryFrom for MempoolEvent { .reverted .into_iter() .map(TransactionId::try_from) - .collect::>()?; + .collect::>() + .context("reverted")?; Ok(Self::TransactionsReverted(txs)) }, diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index 4379e266a2..040fd700ae 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -4,7 +4,7 @@ use miden_protocol::crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath}; use crate::domain::{convert, try_convert}; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::generated as proto; // MERKLE PATH @@ -62,7 +62,8 @@ impl TryFrom for SparseMerklePath { .siblings .into_iter() .map(Word::try_from) - .collect::, _>>()?, + .collect::, _>>() + .context("siblings")?, )?) } } @@ -84,12 +85,16 @@ impl TryFrom for MmrDelta { type Error = ConversionError; fn try_from(value: proto::primitives::MmrDelta) -> Result { - let data: Result, ConversionError> = - value.data.into_iter().map(Word::try_from).collect(); + let data: Vec<_> = value + .data + .into_iter() + .map(Word::try_from) + .collect::>() + .context("data")?; Ok(MmrDelta { forest: Forest::new(value.forest as usize), - data: data?, + data, }) } } @@ -113,13 +118,13 @@ impl TryFrom for SmtLeaf { Ok(Self::new_empty(LeafIndex::new_max_depth(leaf_index))) }, proto::primitives::smt_leaf::Leaf::Single(entry) => { - let (key, value): (Word, Word) = entry.try_into()?; + let (key, value): (Word, Word) = entry.try_into().context("single")?; Ok(SmtLeaf::new_single(key, value)) }, proto::primitives::smt_leaf::Leaf::Multiple(entries) => { let domain_entries: Vec<(Word, Word)> = - try_convert(entries.entries).collect::>()?; + try_convert(entries.entries).collect::>().context("multiple")?; Ok(SmtLeaf::new_multiple(domain_entries)?) }, @@ -155,13 +160,15 @@ impl TryFrom for (Word, Word) { .ok_or(ConversionError::missing_field::(stringify!( key )))? - .try_into()?; + .try_into() + .context("key")?; let value: Word = entry .value .ok_or(ConversionError::missing_field::(stringify!( value )))? - .try_into()?; + .try_into() + .context("value")?; Ok((key, value)) } @@ -188,13 +195,15 @@ impl TryFrom for SmtProof { .ok_or(ConversionError::missing_field::(stringify!( path )))? - .try_into()?; + .try_into() + .context("path")?; let leaf: SmtLeaf = opening .leaf .ok_or(ConversionError::missing_field::(stringify!( leaf )))? - .try_into()?; + .try_into() + .context("leaf")?; Ok(SmtProof::new(path, leaf)?) } diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index a1eda1603a..08ad922c22 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -17,7 +17,7 @@ use miden_protocol::utils::{Deserializable, Serializable}; use miden_protocol::{MastForest, MastNodeId, Word}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::generated as proto; // NOTE TYPE @@ -58,10 +58,12 @@ impl TryFrom for NoteMetadata { .ok_or_else(|| { ConversionError::missing_field::(stringify!(sender)) })? - .try_into()?; + .try_into() + .context("sender")?; let note_type = proto::note::NoteType::try_from(value.note_type) .map_err(|_| ConversionError::message("enum variant discriminant out of range"))? - .try_into()?; + .try_into() + .context("note_type")?; let tag = NoteTag::new(value.tag); // Deserialize attachment if present @@ -116,7 +118,8 @@ impl TryFrom for AccountTargetNetworkNote { .ok_or_else(|| { ConversionError::missing_field::(stringify!(metadata)) })? - .try_into()?; + .try_into() + .context("metadata")?; let note = Note::new(assets, metadata, recipient); AccountTargetNetworkNote::new(note).map_err(ConversionError::from) } @@ -182,7 +185,8 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion stringify!(inclusion_path), ))? .clone(), - )?; + ) + .context("inclusion_path")?; let note_id = Word::try_from( proof @@ -194,13 +198,14 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion .id .as_ref() .ok_or(ConversionError::missing_field::(stringify!(id)))?, - )?; + ) + .context("note_id")?; Ok(( NoteId::from_raw(note_id), NoteInclusionProof::new( proof.block_num.into(), - proof.note_index_in_block.try_into()?, + proof.note_index_in_block.try_into().context("note_index_in_block")?, inclusion_path, )?, )) @@ -214,7 +219,8 @@ impl TryFrom for Note { let metadata: NoteMetadata = proto_note .metadata .ok_or(ConversionError::missing_field::(stringify!(metadata)))? - .try_into()?; + .try_into() + .context("metadata")?; let details = proto_note .details @@ -249,13 +255,15 @@ impl TryFrom for NoteHeader { .ok_or_else(|| { ConversionError::missing_field::(stringify!(note_id)) })? - .try_into()?; + .try_into() + .context("note_id")?; let metadata: NoteMetadata = value .metadata .ok_or_else(|| { ConversionError::missing_field::(stringify!(metadata)) })? - .try_into()?; + .try_into() + .context("metadata")?; Ok(NoteHeader::new(NoteId::from_raw(note_id_word), metadata)) } diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index 319137f49c..0277d946a7 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -2,7 +2,7 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::smt::SmtProof; use miden_protocol::note::Nullifier; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::generated as proto; // FROM NULLIFIER @@ -53,13 +53,15 @@ impl TryFrom for NullifierWitnessR .ok_or(ConversionError::missing_field::< proto::store::block_inputs::NullifierWitness, >(stringify!(nullifier)))? - .try_into()?, + .try_into() + .context("nullifier")?, proof: nullifier_witness_record .opening .ok_or(ConversionError::missing_field::< proto::store::block_inputs::NullifierWitness, >(stringify!(opening)))? - .try_into()?, + .try_into() + .context("opening")?, }) } } diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index 5b057281fe..e7b538d5c5 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -3,7 +3,7 @@ use miden_protocol::note::Nullifier; use miden_protocol::transaction::{InputNoteCommitment, TransactionId}; use miden_protocol::utils::{Deserializable, Serializable}; -use crate::errors::ConversionError; +use crate::errors::{ConversionError, ConversionResultExt}; use crate::generated as proto; // FROM TRANSACTION ID @@ -53,6 +53,7 @@ impl TryFrom for TransactionId { .id .ok_or(ConversionError::missing_field::("id"))? .try_into() + .context("id") } } @@ -79,10 +80,11 @@ impl TryFrom for InputNoteCommitment { stringify!(nullifier), ) })? - .try_into()?; + .try_into() + .context("nullifier")?; let header: Option = - value.header.map(TryInto::try_into).transpose()?; + value.header.map(TryInto::try_into).transpose().context("header")?; // TODO: https://github.com/0xMiden/node/issues/1783 // InputNoteCommitment has private fields, so we reconstruct it via diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 5f4788d433..f4fe9c5f10 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -141,6 +141,33 @@ impl fmt::Display for StringError { impl std::error::Error for StringError {} +// CONVERSION RESULT EXTENSION TRAIT +// ================================================================================================ + +/// Extension trait to ergonomically add field context to [`ConversionError`] results. +/// +/// This makes it easy to inject field names into the error path at each `?` site: +/// +/// ```rust,ignore +/// let account_root = value.account_root +/// .ok_or(ConversionError::missing_field::("account_root"))? +/// .try_into() +/// .context("account_root")?; +/// ``` +/// +/// The context stacks automatically through nested conversions, producing error paths like +/// `"header.account_root: value is not in range 0..MODULUS"`. +pub trait ConversionResultExt { + /// Add field context to the error, wrapping it in a [`ConversionError`] if needed. + fn context(self, field: &'static str) -> Result; +} + +impl> ConversionResultExt for Result { + fn context(self, field: &'static str) -> Result { + self.map_err(|e| e.into().context(field)) + } +} + // FROM IMPLS FOR EXTERNAL ERROR TYPES // ================================================================================================ From a2e57e2c0504d46a33b1d44d750ef6aafeb5281f Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 13:45:41 +1300 Subject: [PATCH 04/26] add impl_from_for_conversion_error --- crates/proto/src/errors/mod.rs | 104 +++++++++------------------------ 1 file changed, 27 insertions(+), 77 deletions(-) diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index f4fe9c5f10..091e373cfb 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -171,80 +171,30 @@ impl> ConversionResultExt for Result { // FROM IMPLS FOR EXTERNAL ERROR TYPES // ================================================================================================ -impl From for ConversionError { - fn from(e: hex::FromHexError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::errors::AccountError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::errors::AssetError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::errors::FeeError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::errors::NoteError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::errors::StorageSlotNameError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::crypto::merkle::MerkleError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::crypto::merkle::smt::SmtLeafError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::crypto::merkle::smt::SmtProofError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: std::num::TryFromIntError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_standards::note::NetworkAccountTargetError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: DeserializationError) -> Self { - Self::new(e) - } -} - -impl From for ConversionError { - fn from(e: miden_protocol::errors::AssetVaultError) -> Self { - Self::new(e) - } -} +macro_rules! impl_from_for_conversion_error { + ($($ty:ty),* $(,)?) => { + $( + impl From<$ty> for ConversionError { + fn from(e: $ty) -> Self { + Self::new(e) + } + } + )* + }; +} + +impl_from_for_conversion_error!( + hex::FromHexError, + miden_protocol::errors::AccountError, + miden_protocol::errors::AssetError, + miden_protocol::errors::AssetVaultError, + miden_protocol::errors::FeeError, + miden_protocol::errors::NoteError, + miden_protocol::errors::StorageSlotNameError, + miden_protocol::crypto::merkle::MerkleError, + miden_protocol::crypto::merkle::smt::SmtLeafError, + miden_protocol::crypto::merkle::smt::SmtProofError, + miden_standards::note::NetworkAccountTargetError, + std::num::TryFromIntError, + DeserializationError, +); From a7266b6bb44906a9e393ba5942b4306c11065f23 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 14:04:00 +1300 Subject: [PATCH 05/26] more context --- crates/block-producer/src/store/mod.rs | 11 ++++++---- crates/ntx-builder/src/clients/store.rs | 25 +++++++++++++++-------- crates/store/src/server/api.rs | 7 ++++++- crates/store/src/server/block_producer.rs | 11 ++++++---- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index 79da468f53..e4eae10701 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -5,7 +5,7 @@ use std::num::NonZeroU32; use itertools::Itertools; use miden_node_proto::clients::{Builder, StoreBlockProducerClient}; use miden_node_proto::domain::batch::BatchInputs; -use miden_node_proto::errors::ConversionError; +use miden_node_proto::errors::{ConversionError, ConversionResultExt}; use miden_node_proto::{AccountState, generated as proto}; use miden_node_utils::formatting::format_opt; use miden_protocol::Word; @@ -75,7 +75,8 @@ impl TryFrom for TransactionInputs { .ok_or(ConversionError::missing_field::(stringify!( account_state )))? - .try_into()?; + .try_into() + .context("account_state")?; let mut nullifiers = HashMap::new(); for nullifier_record in response.nullifiers { @@ -84,7 +85,8 @@ impl TryFrom for TransactionInputs { .ok_or(ConversionError::missing_field::< proto::store::transaction_inputs::NullifierTransactionInputRecord, >(stringify!(nullifier)))? - .try_into()?; + .try_into() + .context("nullifier")?; // Note that this intentionally maps 0 to None as this is the definition used in // protobuf. @@ -95,7 +97,8 @@ impl TryFrom for TransactionInputs { .found_unauthenticated_notes .into_iter() .map(Word::try_from) - .collect::>()?; + .collect::>() + .context("found_unauthenticated_notes")?; let current_block_height = response.block_height.into(); diff --git a/crates/ntx-builder/src/clients/store.rs b/crates/ntx-builder/src/clients/store.rs index 435e499cf3..494ffe88b1 100644 --- a/crates/ntx-builder/src/clients/store.rs +++ b/crates/ntx-builder/src/clients/store.rs @@ -4,7 +4,7 @@ use std::time::Duration; use miden_node_proto::clients::{Builder, StoreNtxBuilderClient}; use miden_node_proto::domain::account::{AccountDetails, AccountResponse, NetworkAccountId}; -use miden_node_proto::errors::ConversionError; +use miden_node_proto::errors::{ConversionError, ConversionResultExt}; use miden_node_proto::generated::rpc::BlockRange; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -103,6 +103,7 @@ impl StoreClient { Some(block) => { let peaks: Vec = try_convert(response.current_peaks) .collect::>() + .context("current_peaks") .map_err(StoreError::DeserializationError)?; let header = BlockHeader::try_from(block).map_err(StoreError::DeserializationError)?; @@ -142,7 +143,9 @@ impl StoreClient { // which implies details being public, so OK to error otherwise let account = match store_response.map(|acc| acc.details) { Some(Some(details)) => Some(Account::read_from_bytes(&details).map_err(|err| { - StoreError::DeserializationError(ConversionError::deserialization("account", err)) + StoreError::DeserializationError( + ConversionError::from(err).context("account_details"), + ) })?), _ => None, }; @@ -321,10 +324,9 @@ impl StoreClient { .into_iter() .map(|account_id| { let account_id = AccountId::read_from_bytes(&account_id.id).map_err(|err| { - StoreError::DeserializationError(ConversionError::deserialization( - "account_id", - err, - )) + StoreError::DeserializationError( + ConversionError::from(err).context("account_id"), + ) })?; NetworkAccountId::try_from(account_id).map_err(|_| { StoreError::MalformedResponse( @@ -407,8 +409,10 @@ impl StoreClient { let smt_opening = asset_witness.proof.ok_or_else(|| { StoreError::MalformedResponse("missing proof in vault asset witness".to_string()) })?; - let proof: SmtProof = - smt_opening.try_into().map_err(StoreError::DeserializationError)?; + let proof: SmtProof = smt_opening + .try_into() + .context("proof") + .map_err(StoreError::DeserializationError)?; let witness = AssetWitness::new(proof) .map_err(|err| StoreError::DeserializationError(ConversionError::from(err)))?; @@ -446,7 +450,10 @@ impl StoreClient { StoreError::MalformedResponse("missing proof in storage map witness".to_string()) })?; - let proof: SmtProof = smt_opening.try_into().map_err(StoreError::DeserializationError)?; + let proof: SmtProof = smt_opening + .try_into() + .context("proof") + .map_err(StoreError::DeserializationError)?; // Create the storage map witness using the proof and raw map key. let witness = StorageMapWitness::new(proof, [map_key]).map_err(|_err| { diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 543ab1257e..a076b3fad6 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use std::sync::Arc; -use miden_node_proto::errors::ConversionError; +use miden_node_proto::errors::{ConversionError, ConversionResultExt}; use miden_node_proto::generated as proto; use miden_node_utils::ErrorReport; use miden_protocol::Word; @@ -124,6 +124,7 @@ where { root.ok_or_else(|| ConversionError::message(format!("{entity}: missing field `root`")))? .try_into() + .context("root") .map_err(|e: ConversionError| e.into()) } @@ -139,6 +140,7 @@ where .into_iter() .map(TryInto::try_into) .collect::, ConversionError>>() + .context("digests") .map_err(Into::into) } @@ -152,6 +154,7 @@ where .cloned() .map(AccountId::try_from) .collect::>() + .context("account_ids") .map_err(Into::into) } @@ -161,6 +164,7 @@ where { id.ok_or_else(|| ConversionError::message("missing account ID"))? .try_into() + .context("account_id") .map_err(|e: ConversionError| e.into()) } @@ -180,6 +184,7 @@ where .copied() .map(Nullifier::try_from) .collect::>() + .context("nullifiers") .map_err(Into::into) } diff --git a/crates/store/src/server/block_producer.rs b/crates/store/src/server/block_producer.rs index e3188190b8..e4014bcfb2 100644 --- a/crates/store/src/server/block_producer.rs +++ b/crates/store/src/server/block_producer.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; use futures::TryFutureExt; use miden_crypto::dsa::ecdsa_k256_keccak::Signature; -use miden_node_proto::errors::ConversionError; +use miden_node_proto::errors::{ConversionError, ConversionResultExt}; use miden_node_proto::generated::store::block_producer_server; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -64,21 +64,24 @@ impl block_producer_server::BlockProducer for StoreApi { .ok_or(ConversionError::missing_field::(stringify!( header )))? - .try_into()?; + .try_into() + .context("header")?; // Read block body. let body: BlockBody = block .body .ok_or(ConversionError::missing_field::(stringify!( body )))? - .try_into()?; + .try_into() + .context("body")?; // Read signature. let signature: Signature = block .signature .ok_or(ConversionError::missing_field::(stringify!( signature )))? - .try_into()?; + .try_into() + .context("signature")?; // Get block inputs from ordered batches. let block_inputs = From 097d247132a6bf0668ff42bd324216620769f55d Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 17:38:35 +1300 Subject: [PATCH 06/26] unit tests --- crates/proto/src/errors/mod.rs | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 091e373cfb..59788cb792 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -198,3 +198,55 @@ impl_from_for_conversion_error!( std::num::TryFromIntError, DeserializationError, ); + +#[cfg(test)] +mod tests { + use super::*; + + /// Simulates a deeply nested conversion where each layer adds its field context. + fn inner_conversion() -> Result<(), ConversionError> { + Err(ConversionError::message("value is not in range 0..MODULUS")) + } + + fn mid_conversion() -> Result<(), ConversionError> { + inner_conversion().context("account_root") + } + + fn outer_conversion() -> Result<(), ConversionError> { + mid_conversion().context("header") + } + + #[test] + fn test_context_builds_dotted_field_path() { + let err = outer_conversion().unwrap_err(); + assert_eq!(err.to_string(), "header.account_root: value is not in range 0..MODULUS"); + } + + #[test] + fn test_context_single_field() { + let err = inner_conversion().context("nullifier").unwrap_err(); + assert_eq!(err.to_string(), "nullifier: value is not in range 0..MODULUS"); + } + + #[test] + fn test_context_deep_nesting() { + let err = outer_conversion().context("block").context("response").unwrap_err(); + assert_eq!( + err.to_string(), + "response.block.header.account_root: value is not in range 0..MODULUS" + ); + } + + #[test] + fn test_no_context_shows_source_only() { + let err = inner_conversion().unwrap_err(); + assert_eq!(err.to_string(), "value is not in range 0..MODULUS"); + } + + #[test] + fn test_context_on_external_error_type() { + let result: Result = u8::try_from(256u16); + let err = result.context("fee_amount").unwrap_err(); + assert!(err.to_string().starts_with("fee_amount: "), "expected field prefix, got: {err}",); + } +} From eaf4c39447d0eaad9fb5d3f44250c908dea5b9b5 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 17:39:55 +1300 Subject: [PATCH 07/26] rm mid_conversion --- crates/proto/src/errors/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 59788cb792..4a0ed40ed6 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -208,12 +208,8 @@ mod tests { Err(ConversionError::message("value is not in range 0..MODULUS")) } - fn mid_conversion() -> Result<(), ConversionError> { - inner_conversion().context("account_root") - } - fn outer_conversion() -> Result<(), ConversionError> { - mid_conversion().context("header") + inner_conversion().context("account_root").context("header") } #[test] From 14c37138703872777036c84e565e71110cf6037c Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 17:43:25 +1300 Subject: [PATCH 08/26] update comment --- crates/proto/src/errors/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 4a0ed40ed6..a005d1ad99 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -36,6 +36,11 @@ impl ConversionError { /// /// Called from inner to outer, so the path accumulates in reverse /// (outermost field pushed last). + /// + /// Use this to annotate errors from `try_into()` / `try_from()` where the underlying + /// error has no knowledge of which field it originated from. Do not use it with + /// [`missing_field`](Self::missing_field) which already embeds the field name in its + /// message. #[must_use] pub fn context(mut self, field: &'static str) -> Self { self.path.push(field); From 7b84c6cb0ced1add9878211f1f67bfd2466b7966 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 17:45:38 +1300 Subject: [PATCH 09/26] fix context field details --- crates/ntx-builder/src/clients/store.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/ntx-builder/src/clients/store.rs b/crates/ntx-builder/src/clients/store.rs index 494ffe88b1..38ac2c9ea0 100644 --- a/crates/ntx-builder/src/clients/store.rs +++ b/crates/ntx-builder/src/clients/store.rs @@ -143,9 +143,7 @@ impl StoreClient { // which implies details being public, so OK to error otherwise let account = match store_response.map(|acc| acc.details) { Some(Some(details)) => Some(Account::read_from_bytes(&details).map_err(|err| { - StoreError::DeserializationError( - ConversionError::from(err).context("account_details"), - ) + StoreError::DeserializationError(ConversionError::from(err).context("details")) })?), _ => None, }; From 132152baf3394810e0dae7685e5584921809dabb Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 17:47:34 +1300 Subject: [PATCH 10/26] fix field missing --- crates/ntx-builder/src/clients/store.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/ntx-builder/src/clients/store.rs b/crates/ntx-builder/src/clients/store.rs index 38ac2c9ea0..0d11dc1fc6 100644 --- a/crates/ntx-builder/src/clients/store.rs +++ b/crates/ntx-builder/src/clients/store.rs @@ -488,10 +488,11 @@ pub fn build_minimal_foreign_account( account_details: &AccountDetails, ) -> Result { // Derive account code. - let account_code_bytes = account_details - .account_code - .as_ref() - .ok_or_else(|| ConversionError::message("account code missing"))?; + let account_code_bytes = account_details.account_code.as_ref().ok_or_else(|| { + ConversionError::missing_field::( + "account_code", + ) + })?; let account_code = AccountCode::from_bytes(account_code_bytes)?; // Derive partial storage. Storage maps are not required for foreign accounts. From 4a921dc4c797a836076937258c6d10e2b416fc0e Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Mar 2026 17:53:04 +1300 Subject: [PATCH 11/26] fix nonce map_err --- crates/proto/src/domain/account.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index eac0e55977..5c4b643b4a 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -309,9 +309,7 @@ impl TryFrom for AccountHeader { )))? .try_into() .context("code_commitment")?; - let nonce = nonce - .try_into() - .map_err(|_e| ConversionError::message("value is not in the range 0..MODULUS"))?; + let nonce = nonce.try_into().map_err(ConversionError::message).context("nonce")?; Ok(AccountHeader::new( account_id, From a59b28128600f702b581d6be5498766619444ed6 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 13:41:32 +1300 Subject: [PATCH 12/26] fix more use cases --- crates/proto/src/domain/merkle.rs | 4 ++-- crates/store/src/server/api.rs | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index 040fd700ae..ca157ccc00 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -118,13 +118,13 @@ impl TryFrom for SmtLeaf { Ok(Self::new_empty(LeafIndex::new_max_depth(leaf_index))) }, proto::primitives::smt_leaf::Leaf::Single(entry) => { - let (key, value): (Word, Word) = entry.try_into().context("single")?; + let (key, value): (Word, Word) = entry.try_into().context("entry")?; Ok(SmtLeaf::new_single(key, value)) }, proto::primitives::smt_leaf::Leaf::Multiple(entries) => { let domain_entries: Vec<(Word, Word)> = - try_convert(entries.entries).collect::>().context("multiple")?; + try_convert(entries.entries).collect::>().context("entries")?; Ok(SmtLeaf::new_multiple(domain_entries)?) }, diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index a076b3fad6..5451157e9f 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -162,10 +162,12 @@ pub fn read_account_id(id: Option) -> Result, { - id.ok_or_else(|| ConversionError::message("missing account ID"))? - .try_into() - .context("account_id") - .map_err(|e: ConversionError| e.into()) + id.ok_or_else(|| { + ConversionError::missing_field::(stringify!(account_id)) + })? + .try_into() + .context("account_id") + .map_err(|e: ConversionError| e.into()) } #[instrument( From 81838af5ada64f787ef5d5755918d3d561a5f80c Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 13:48:05 +1300 Subject: [PATCH 13/26] remove stringify --- crates/block-producer/src/store/mod.rs | 6 +- crates/proto/src/domain/account.rs | 80 ++++++++--------------- crates/proto/src/domain/block.rs | 36 ++++------ crates/proto/src/domain/merkle.rs | 18 ++--- crates/proto/src/domain/note.rs | 20 +++--- crates/proto/src/domain/nullifier.rs | 4 +- crates/proto/src/domain/transaction.rs | 2 +- crates/store/src/server/api.rs | 2 +- crates/store/src/server/block_producer.rs | 14 ++-- crates/store/src/server/rpc_api.rs | 4 +- 10 files changed, 65 insertions(+), 121 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index e4eae10701..fce26a1d40 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -72,9 +72,7 @@ impl TryFrom for TransactionInputs { fn try_from(response: proto::store::TransactionInputs) -> Result { let AccountState { account_id, account_commitment } = response .account_state - .ok_or(ConversionError::missing_field::(stringify!( - account_state - )))? + .ok_or(ConversionError::missing_field::("account_state"))? .try_into() .context("account_state")?; @@ -84,7 +82,7 @@ impl TryFrom for TransactionInputs { .nullifier .ok_or(ConversionError::missing_field::< proto::store::transaction_inputs::NullifierTransactionInputRecord, - >(stringify!(nullifier)))? + >("nullifier"))? .try_into() .context("nullifier")?; diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 5c4b643b4a..57726f6f02 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -158,9 +158,7 @@ impl TryFrom for AccountRequest { let proto::rpc::AccountRequest { account_id, block_num, details } = value; let account_id = account_id - .ok_or(ConversionError::missing_field::(stringify!( - account_id - )))? + .ok_or(ConversionError::missing_field::("account_id"))? .try_into() .context("account_id")?; let block_num = block_num.map(Into::into); @@ -230,7 +228,7 @@ impl TryFrom(stringify!(slot_data)))? + >("slot_data"))? .try_into() .context("slot_data")?; @@ -286,27 +284,19 @@ impl TryFrom for AccountHeader { } = value; let account_id = account_id - .ok_or(ConversionError::missing_field::(stringify!( - account_id - )))? + .ok_or(ConversionError::missing_field::("account_id"))? .try_into() .context("account_id")?; let vault_root = vault_root - .ok_or(ConversionError::missing_field::(stringify!( - vault_root - )))? + .ok_or(ConversionError::missing_field::("vault_root"))? .try_into() .context("vault_root")?; let storage_commitment = storage_commitment - .ok_or(ConversionError::missing_field::(stringify!( - storage_commitment - )))? + .ok_or(ConversionError::missing_field::("storage_commitment"))? .try_into() .context("storage_commitment")?; let code_commitment = code_commitment - .ok_or(ConversionError::missing_field::(stringify!( - code_commitment - )))? + .ok_or(ConversionError::missing_field::("code_commitment"))? .try_into() .context("code_commitment")?; let nonce = nonce.try_into().map_err(ConversionError::message).context("nonce")?; @@ -407,7 +397,7 @@ impl TryFrom for AccountVaultDetails { Result::, ConversionError>::from_iter(assets.into_iter().map(|asset| { let asset = asset.asset.ok_or(ConversionError::missing_field::< proto::primitives::Asset, - >(stringify!(asset)))?; + >("asset"))?; let asset = Word::try_from(asset).context("asset")?; Asset::try_from(asset).map_err(ConversionError::from) })) @@ -575,7 +565,7 @@ impl TryFrom None => { return Err(ConversionError::missing_field::< proto::rpc::account_storage_details::AccountStorageMapDetails, - >(stringify!(entries))); + >("entries")); }, Some(ProtoEntries::AllEntries(AllMapEntries { entries })) => { let entries = entries @@ -584,7 +574,7 @@ impl TryFrom let key = entry .key .ok_or(ConversionError::missing_field::( - stringify!(key), + "key", ))? .try_into() .map(StorageMapKey::new) @@ -592,7 +582,7 @@ impl TryFrom let value = entry .value .ok_or(ConversionError::missing_field::( - stringify!(value), + "value", ))? .try_into() .context("value")?; @@ -609,9 +599,7 @@ impl TryFrom let smt_opening = entry.proof.ok_or(ConversionError::missing_field::< StorageMapEntryWithProof, - >(stringify!( - proof - )))?; + >("proof"))?; SmtProof::try_from(smt_opening).context("proof") }) .collect::, ConversionError>>() @@ -715,7 +703,7 @@ impl TryFrom for AccountStorageDetails { let header = header .ok_or(ConversionError::missing_field::( - stringify!(header), + "header", ))? .try_into() .context("header")?; @@ -770,15 +758,11 @@ impl TryFrom for AccountResponse { let proto::rpc::AccountResponse { block_num, witness, details } = value; let block_num = block_num - .ok_or(ConversionError::missing_field::(stringify!( - block_num - )))? + .ok_or(ConversionError::missing_field::("block_num"))? .into(); let witness = witness - .ok_or(ConversionError::missing_field::(stringify!( - witness - )))? + .ok_or(ConversionError::missing_field::("witness"))? .try_into() .context("witness")?; @@ -842,21 +826,21 @@ impl TryFrom for AccountDetails { let account_header = header .ok_or(ConversionError::missing_field::( - stringify!(header), + "header", ))? .try_into() .context("header")?; let storage_details = storage_details .ok_or(ConversionError::missing_field::( - stringify!(storage_details), + "storage_details", ))? .try_into() .context("storage_details")?; let vault_details = vault_details .ok_or(ConversionError::missing_field::( - stringify!(vault_details), + "vault_details", ))? .try_into() .context("vault_details")?; @@ -903,23 +887,17 @@ impl TryFrom for AccountWitness { fn try_from(account_witness: proto::account::AccountWitness) -> Result { let witness_id = account_witness .witness_id - .ok_or(ConversionError::missing_field::(stringify!( - witness_id - )))? + .ok_or(ConversionError::missing_field::("witness_id"))? .try_into() .context("witness_id")?; let commitment = account_witness .commitment - .ok_or(ConversionError::missing_field::(stringify!( - commitment - )))? + .ok_or(ConversionError::missing_field::("commitment"))? .try_into() .context("commitment")?; let path = account_witness .path - .ok_or(ConversionError::missing_field::(stringify!( - path - )))? + .ok_or(ConversionError::missing_field::("path"))? .try_into() .context("path")?; @@ -960,24 +938,18 @@ impl TryFrom for AccountWitnessRecord { ) -> Result { let witness_id = account_witness_record .witness_id - .ok_or(ConversionError::missing_field::(stringify!( - witness_id - )))? + .ok_or(ConversionError::missing_field::("witness_id"))? .try_into() .context("witness_id")?; let commitment = account_witness_record .commitment - .ok_or(ConversionError::missing_field::(stringify!( - commitment - )))? + .ok_or(ConversionError::missing_field::("commitment"))? .try_into() .context("commitment")?; let path: SparseMerklePath = account_witness_record .path .as_ref() - .ok_or(ConversionError::missing_field::(stringify!( - path - )))? + .ok_or(ConversionError::missing_field::("path"))? .clone() .try_into() .context("path")?; @@ -993,7 +965,7 @@ impl TryFrom for AccountWitnessRecord { account_id: account_witness_record .account_id .ok_or(ConversionError::missing_field::( - stringify!(account_id), + "account_id", ))? .try_into() .context("account_id")?, @@ -1045,7 +1017,7 @@ impl TryFrom fo .account_id .ok_or(ConversionError::missing_field::< proto::store::transaction_inputs::AccountTransactionInputRecord, - >(stringify!(account_id)))? + >("account_id"))? .try_into() .context("account_id")?; @@ -1053,7 +1025,7 @@ impl TryFrom fo .account_commitment .ok_or(ConversionError::missing_field::< proto::store::transaction_inputs::AccountTransactionInputRecord, - >(stringify!(account_commitment)))? + >("account_commitment"))? .try_into() .context("account_commitment")?; diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 7d87adb3c2..f03a4f50ea 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -80,7 +80,7 @@ impl TryFrom for BlockHeader { value .prev_block_commitment .ok_or(ConversionError::missing_field::( - stringify!(prev_block_commitment), + "prev_block_commitment", ))? .try_into() .context("prev_block_commitment")?, @@ -88,56 +88,54 @@ impl TryFrom for BlockHeader { value .chain_commitment .ok_or(ConversionError::missing_field::( - stringify!(chain_commitment), + "chain_commitment", ))? .try_into() .context("chain_commitment")?, value .account_root .ok_or(ConversionError::missing_field::( - stringify!(account_root), + "account_root", ))? .try_into() .context("account_root")?, value .nullifier_root .ok_or(ConversionError::missing_field::( - stringify!(nullifier_root), + "nullifier_root", ))? .try_into() .context("nullifier_root")?, value .note_root .ok_or(ConversionError::missing_field::( - stringify!(note_root), + "note_root", ))? .try_into() .context("note_root")?, value .tx_commitment .ok_or(ConversionError::missing_field::( - stringify!(tx_commitment), + "tx_commitment", ))? .try_into() .context("tx_commitment")?, value .tx_kernel_commitment .ok_or(ConversionError::missing_field::( - stringify!(tx_kernel_commitment), + "tx_kernel_commitment", ))? .try_into() .context("tx_kernel_commitment")?, value .validator_key .ok_or(ConversionError::missing_field::( - stringify!(validator_key), + "validator_key", ))? .try_into() .context("validator_key")?, FeeParameters::try_from(value.fee_parameters.ok_or( - ConversionError::missing_field::(stringify!( - fee_parameters - )), + ConversionError::missing_field::("fee_parameters"), )?) .context("fee_parameters")?, value.timestamp, @@ -208,23 +206,17 @@ impl TryFrom for SignedBlock { fn try_from(value: proto::blockchain::SignedBlock) -> Result { let header = value .header - .ok_or(ConversionError::missing_field::(stringify!( - header - )))? + .ok_or(ConversionError::missing_field::("header"))? .try_into() .context("header")?; let body = value .body - .ok_or(ConversionError::missing_field::(stringify!( - body - )))? + .ok_or(ConversionError::missing_field::("body"))? .try_into() .context("body")?; let signature = value .signature - .ok_or(ConversionError::missing_field::(stringify!( - signature - )))? + .ok_or(ConversionError::missing_field::("signature"))? .try_into() .context("signature")?; @@ -374,9 +366,7 @@ impl TryFrom for FeeParameters { let native_asset_id = fee_params .native_asset_id .map(AccountId::try_from) - .ok_or(ConversionError::missing_field::(stringify!( - native_asset_id - )))? + .ok_or(ConversionError::missing_field::("native_asset_id"))? .context("native_asset_id")?; let fee_params = FeeParameters::new(native_asset_id, fee_params.verification_base_fee)?; Ok(fee_params) diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index ca157ccc00..cb33d92ebb 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -110,7 +110,7 @@ impl TryFrom for SmtLeaf { fn try_from(value: proto::primitives::SmtLeaf) -> Result { let leaf = value.leaf.ok_or( - ConversionError::missing_field::(stringify!(leaf)), + ConversionError::missing_field::("leaf"), )?; match leaf { @@ -157,16 +157,12 @@ impl TryFrom for (Word, Word) { fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { let key: Word = entry .key - .ok_or(ConversionError::missing_field::(stringify!( - key - )))? + .ok_or(ConversionError::missing_field::("key"))? .try_into() .context("key")?; let value: Word = entry .value - .ok_or(ConversionError::missing_field::(stringify!( - value - )))? + .ok_or(ConversionError::missing_field::("value"))? .try_into() .context("value")?; @@ -192,16 +188,12 @@ impl TryFrom for SmtProof { fn try_from(opening: proto::primitives::SmtOpening) -> Result { let path: SparseMerklePath = opening .path - .ok_or(ConversionError::missing_field::(stringify!( - path - )))? + .ok_or(ConversionError::missing_field::("path"))? .try_into() .context("path")?; let leaf: SmtLeaf = opening .leaf - .ok_or(ConversionError::missing_field::(stringify!( - leaf - )))? + .ok_or(ConversionError::missing_field::("leaf"))? .try_into() .context("leaf")?; diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 08ad922c22..202ead1321 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -56,7 +56,7 @@ impl TryFrom for NoteMetadata { let sender = value .sender .ok_or_else(|| { - ConversionError::missing_field::(stringify!(sender)) + ConversionError::missing_field::("sender") })? .try_into() .context("sender")?; @@ -116,7 +116,7 @@ impl TryFrom for AccountTargetNetworkNote { let metadata: NoteMetadata = value .metadata .ok_or_else(|| { - ConversionError::missing_field::(stringify!(metadata)) + ConversionError::missing_field::("metadata") })? .try_into() .context("metadata")?; @@ -149,7 +149,7 @@ impl TryFrom for Word { note_id .id .as_ref() - .ok_or(ConversionError::missing_field::(stringify!(id)))? + .ok_or(ConversionError::missing_field::("id"))? .try_into() } } @@ -182,7 +182,7 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion .inclusion_path .as_ref() .ok_or(ConversionError::missing_field::( - stringify!(inclusion_path), + "inclusion_path", ))? .clone(), ) @@ -193,11 +193,11 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion .note_id .as_ref() .ok_or(ConversionError::missing_field::( - stringify!(note_id), + "note_id", ))? .id .as_ref() - .ok_or(ConversionError::missing_field::(stringify!(id)))?, + .ok_or(ConversionError::missing_field::("id"))?, ) .context("note_id")?; @@ -218,13 +218,13 @@ impl TryFrom for Note { fn try_from(proto_note: proto::note::Note) -> Result { let metadata: NoteMetadata = proto_note .metadata - .ok_or(ConversionError::missing_field::(stringify!(metadata)))? + .ok_or(ConversionError::missing_field::("metadata"))? .try_into() .context("metadata")?; let details = proto_note .details - .ok_or(ConversionError::missing_field::(stringify!(details)))?; + .ok_or(ConversionError::missing_field::("details"))?; let note_details = NoteDetails::read_from_bytes(&details) .map_err(|err| ConversionError::deserialization("NoteDetails", err))?; @@ -253,14 +253,14 @@ impl TryFrom for NoteHeader { let note_id_word: Word = value .note_id .ok_or_else(|| { - ConversionError::missing_field::(stringify!(note_id)) + ConversionError::missing_field::("note_id") })? .try_into() .context("note_id")?; let metadata: NoteMetadata = value .metadata .ok_or_else(|| { - ConversionError::missing_field::(stringify!(metadata)) + ConversionError::missing_field::("metadata") })? .try_into() .context("metadata")?; diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index 0277d946a7..2767fb8a06 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -52,14 +52,14 @@ impl TryFrom for NullifierWitnessR .nullifier .ok_or(ConversionError::missing_field::< proto::store::block_inputs::NullifierWitness, - >(stringify!(nullifier)))? + >("nullifier"))? .try_into() .context("nullifier")?, proof: nullifier_witness_record .opening .ok_or(ConversionError::missing_field::< proto::store::block_inputs::NullifierWitness, - >(stringify!(opening)))? + >("opening"))? .try_into() .context("opening")?, }) diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index e7b538d5c5..8536857a4e 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -77,7 +77,7 @@ impl TryFrom for InputNoteCommitment { .nullifier .ok_or_else(|| { ConversionError::missing_field::( - stringify!(nullifier), + "nullifier", ) })? .try_into() diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 5451157e9f..5adbf8a10a 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -163,7 +163,7 @@ where E: From, { id.ok_or_else(|| { - ConversionError::missing_field::(stringify!(account_id)) + ConversionError::missing_field::("account_id") })? .try_into() .context("account_id") diff --git a/crates/store/src/server/block_producer.rs b/crates/store/src/server/block_producer.rs index e4014bcfb2..49a4ba7cc5 100644 --- a/crates/store/src/server/block_producer.rs +++ b/crates/store/src/server/block_producer.rs @@ -57,29 +57,23 @@ impl block_producer_server::BlockProducer for StoreApi { // Read block. let block = request.block.ok_or(ConversionError::missing_field::< proto::store::ApplyBlockRequest, - >(stringify!(block)))?; + >("block"))?; // Read block header. let header: BlockHeader = block .header - .ok_or(ConversionError::missing_field::(stringify!( - header - )))? + .ok_or(ConversionError::missing_field::("header"))? .try_into() .context("header")?; // Read block body. let body: BlockBody = block .body - .ok_or(ConversionError::missing_field::(stringify!( - body - )))? + .ok_or(ConversionError::missing_field::("body"))? .try_into() .context("body")?; // Read signature. let signature: Signature = block .signature - .ok_or(ConversionError::missing_field::(stringify!( - signature - )))? + .ok_or(ConversionError::missing_field::("signature"))? .try_into() .context("signature")?; diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 4b8d6d6780..fa8931de05 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -164,9 +164,7 @@ impl rpc_server::Rpc for StoreApi { let block_range = request .block_range .ok_or_else(|| { - ConversionError::missing_field::(stringify!( - block_range - )) + ConversionError::missing_field::("block_range") }) .map_err(SyncChainMmrError::DeserializationFailed)?; From 2d8709ccfb33816a4432a45bf2fe909989e17b1e Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 13:56:03 +1300 Subject: [PATCH 14/26] fix missing_field uses --- crates/proto/src/domain/block.rs | 10 ++++++---- crates/store/src/server/api.rs | 11 +++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index f03a4f50ea..a621b5227c 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -135,7 +135,7 @@ impl TryFrom for BlockHeader { .try_into() .context("validator_key")?, FeeParameters::try_from(value.fee_parameters.ok_or( - ConversionError::missing_field::("fee_parameters"), + ConversionError::missing_field::("fee_parameters"), )?) .context("fee_parameters")?, value.timestamp, @@ -265,8 +265,8 @@ impl TryFrom for BlockInputs { fn try_from(response: proto::store::BlockInputs) -> Result { let latest_block_header: BlockHeader = response .latest_block_header - .ok_or(ConversionError::missing_field::( - "block_header", + .ok_or(ConversionError::missing_field::( + "latest_block_header", ))? .try_into() .context("latest_block_header")?; @@ -366,7 +366,9 @@ impl TryFrom for FeeParameters { let native_asset_id = fee_params .native_asset_id .map(AccountId::try_from) - .ok_or(ConversionError::missing_field::("native_asset_id"))? + .ok_or(ConversionError::missing_field::( + "native_asset_id", + ))? .context("native_asset_id")?; let fee_params = FeeParameters::new(native_asset_id, fee_params.verification_base_fee)?; Ok(fee_params) diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 5adbf8a10a..6b5c4b4af2 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -162,12 +162,11 @@ pub fn read_account_id(id: Option) -> Result, { - id.ok_or_else(|| { - ConversionError::missing_field::("account_id") - })? - .try_into() - .context("account_id") - .map_err(|e: ConversionError| e.into()) + // Note: not a ConversionError::missing_field because the proto message type is unknown. + id.ok_or_else(|| ConversionError::message("missing account_id"))? + .try_into() + .context("account_id") + .map_err(|e: ConversionError| e.into()) } #[instrument( From 6201041fac8a8caee8d0454aa0c1ea553edb49d3 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 14:00:32 +1300 Subject: [PATCH 15/26] use missing_field in read_account --- crates/proto/src/lib.rs | 1 + crates/store/src/server/api.rs | 7 ++++--- crates/store/src/server/block_producer.rs | 9 +++++---- crates/store/src/server/ntx_builder.rs | 19 ++++++++++++++----- crates/store/src/server/rpc_api.rs | 10 ++++++++-- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/crates/proto/src/lib.rs b/crates/proto/src/lib.rs index 0f5cbb8f51..332044ec51 100644 --- a/crates/proto/src/lib.rs +++ b/crates/proto/src/lib.rs @@ -12,3 +12,4 @@ pub use domain::account::{AccountState, AccountWitnessRecord}; pub use domain::nullifier::NullifierWitnessRecord; pub use domain::proof_request::BlockProofRequest; pub use domain::{convert, try_convert}; +pub use prost; diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 6b5c4b4af2..62f1edb148 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -158,12 +158,13 @@ where .map_err(Into::into) } -pub fn read_account_id(id: Option) -> Result +pub fn read_account_id( + id: Option, +) -> Result where E: From, { - // Note: not a ConversionError::missing_field because the proto message type is unknown. - id.ok_or_else(|| ConversionError::message("missing account_id"))? + id.ok_or_else(|| ConversionError::missing_field::("account_id"))? .try_into() .context("account_id") .map_err(|e: ConversionError| e.into()) diff --git a/crates/store/src/server/block_producer.rs b/crates/store/src/server/block_producer.rs index 49a4ba7cc5..73c94923c5 100644 --- a/crates/store/src/server/block_producer.rs +++ b/crates/store/src/server/block_producer.rs @@ -55,9 +55,9 @@ impl block_producer_server::BlockProducer for StoreApi { ) })?; // Read block. - let block = request.block.ok_or(ConversionError::missing_field::< - proto::store::ApplyBlockRequest, - >("block"))?; + let block = request + .block + .ok_or(ConversionError::missing_field::("block"))?; // Read block header. let header: BlockHeader = block .header @@ -202,7 +202,8 @@ impl block_producer_server::BlockProducer for StoreApi { ) -> Result, Status> { let request = request.into_inner(); - let account_id = read_account_id::(request.account_id)?; + let account_id = + read_account_id::(request.account_id)?; let nullifiers = validate_nullifiers(&request.nullifiers) .map_err(|err| conversion_error_to_status(&err))?; let unauthenticated_note_commitments = diff --git a/crates/store/src/server/ntx_builder.rs b/crates/store/src/server/ntx_builder.rs index f6e8d4a7a5..a55b9c3862 100644 --- a/crates/store/src/server/ntx_builder.rs +++ b/crates/store/src/server/ntx_builder.rs @@ -80,7 +80,8 @@ impl ntx_builder_server::NtxBuilder for StoreApi { &self, request: Request, ) -> Result, Status> { - let account_id = read_account_id::(Some(request.into_inner()))?; + let account_id = + read_account_id::(Some(request.into_inner()))?; let account_info: Option = self.state.get_network_account_details_by_id(account_id).await?; @@ -96,7 +97,9 @@ impl ntx_builder_server::NtxBuilder for StoreApi { ) -> Result, Status> { let request = request.into_inner(); let block_num = BlockNumber::from(request.block_num); - let account_id = read_account_id::(request.account_id)?; + let account_id = read_account_id::( + request.account_id, + )?; let state = self.state.clone(); @@ -206,8 +209,11 @@ impl ntx_builder_server::NtxBuilder for StoreApi { let request = request.into_inner(); // Read account ID. - let account_id = - read_account_id::(request.account_id).map_err(invalid_argument)?; + let account_id = read_account_id::< + proto::store::VaultAssetWitnessesRequest, + GetWitnessesError, + >(request.account_id) + .map_err(invalid_argument)?; // Read vault keys. let vault_keys = request @@ -259,7 +265,10 @@ impl ntx_builder_server::NtxBuilder for StoreApi { // Read the account ID. let account_id = - read_account_id::(request.account_id).map_err(invalid_argument)?; + read_account_id::( + request.account_id, + ) + .map_err(invalid_argument)?; // Read the map key. let map_key = read_root::(request.map_key, "MapKey") diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index fa8931de05..ba9ee1f30b 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -262,7 +262,10 @@ impl rpc_server::Rpc for StoreApi { let request = request.into_inner(); let chain_tip = self.state.latest_block_num().await; - let account_id: AccountId = read_account_id::(request.account_id)?; + let account_id: AccountId = read_account_id::< + proto::rpc::SyncAccountVaultRequest, + SyncAccountVaultError, + >(request.account_id)?; if !account_id.has_public_state() { return Err(SyncAccountVaultError::AccountNotPublic(account_id).into()); @@ -310,7 +313,10 @@ impl rpc_server::Rpc for StoreApi { ) -> Result, Status> { let request = request.into_inner(); - let account_id = read_account_id::(request.account_id)?; + let account_id = read_account_id::< + proto::rpc::SyncAccountStorageMapsRequest, + SyncAccountStorageMapsError, + >(request.account_id)?; if !account_id.has_public_state() { Err(SyncAccountStorageMapsError::AccountNotPublic(account_id))?; From 050591c0ff6287857f45a48c8d531b4f51ff9ca2 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 14:15:33 +1300 Subject: [PATCH 16/26] try_convert_field --- crates/block-producer/src/store/mod.rs | 24 ++- crates/proto/src/domain/account.rs | 180 +++++++++------------- crates/proto/src/domain/batch.rs | 11 +- crates/proto/src/domain/block.rs | 141 +++++++---------- crates/proto/src/domain/mempool.rs | 26 ++-- crates/proto/src/domain/merkle.rs | 36 ++--- crates/proto/src/domain/note.rs | 44 ++---- crates/proto/src/domain/nullifier.rs | 24 ++- crates/proto/src/domain/transaction.rs | 22 +-- crates/proto/src/errors/mod.rs | 70 +++++++++ crates/store/src/server/block_producer.rs | 25 ++- 11 files changed, 274 insertions(+), 329 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index fce26a1d40..a9bb9dcb7c 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -5,7 +5,7 @@ use std::num::NonZeroU32; use itertools::Itertools; use miden_node_proto::clients::{Builder, StoreBlockProducerClient}; use miden_node_proto::domain::batch::BatchInputs; -use miden_node_proto::errors::{ConversionError, ConversionResultExt}; +use miden_node_proto::errors::{ConversionError, ConversionResultExt, try_convert_field}; use miden_node_proto::{AccountState, generated as proto}; use miden_node_utils::formatting::format_opt; use miden_protocol::Word; @@ -70,21 +70,19 @@ impl TryFrom for TransactionInputs { type Error = ConversionError; fn try_from(response: proto::store::TransactionInputs) -> Result { - let AccountState { account_id, account_commitment } = response - .account_state - .ok_or(ConversionError::missing_field::("account_state"))? - .try_into() - .context("account_state")?; + let AccountState { account_id, account_commitment } = + try_convert_field::( + response.account_state, + "account_state", + )?; let mut nullifiers = HashMap::new(); for nullifier_record in response.nullifiers { - let nullifier = nullifier_record - .nullifier - .ok_or(ConversionError::missing_field::< - proto::store::transaction_inputs::NullifierTransactionInputRecord, - >("nullifier"))? - .try_into() - .context("nullifier")?; + let nullifier = try_convert_field::< + proto::store::transaction_inputs::NullifierTransactionInputRecord, + _, + _, + >(nullifier_record.nullifier, "nullifier")?; // Note that this intentionally maps 0 to None as this is the definition used in // protobuf. diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 57726f6f02..3f7f8ce471 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -25,7 +25,7 @@ use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError}; use thiserror::Error; use super::try_convert; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; use crate::generated::{self as proto}; #[cfg(test)] @@ -157,10 +157,8 @@ impl TryFrom for AccountRequest { fn try_from(value: proto::rpc::AccountRequest) -> Result { let proto::rpc::AccountRequest { account_id, block_num, details } = value; - let account_id = account_id - .ok_or(ConversionError::missing_field::("account_id"))? - .try_into() - .context("account_id")?; + let account_id = + try_convert_field::(account_id, "account_id")?; let block_num = block_num.map(Into::into); let details = details.map(TryFrom::try_from).transpose().context("details")?; @@ -225,12 +223,11 @@ impl TryFrom("slot_data"))? - .try_into() - .context("slot_data")?; + let slot_data = try_convert_field::< + proto::rpc::account_request::account_detail_request::StorageMapDetailRequest, + _, + _, + >(slot_data, "slot_data")?; Ok(StorageMapRequest { slot_name, slot_data }) } @@ -283,22 +280,18 @@ impl TryFrom for AccountHeader { nonce, } = value; - let account_id = account_id - .ok_or(ConversionError::missing_field::("account_id"))? - .try_into() - .context("account_id")?; - let vault_root = vault_root - .ok_or(ConversionError::missing_field::("vault_root"))? - .try_into() - .context("vault_root")?; - let storage_commitment = storage_commitment - .ok_or(ConversionError::missing_field::("storage_commitment"))? - .try_into() - .context("storage_commitment")?; - let code_commitment = code_commitment - .ok_or(ConversionError::missing_field::("code_commitment"))? - .try_into() - .context("code_commitment")?; + let account_id = + try_convert_field::(account_id, "account_id")?; + let vault_root = + try_convert_field::(vault_root, "vault_root")?; + let storage_commitment = try_convert_field::( + storage_commitment, + "storage_commitment", + )?; + let code_commitment = try_convert_field::( + code_commitment, + "code_commitment", + )?; let nonce = nonce.try_into().map_err(ConversionError::message).context("nonce")?; Ok(AccountHeader::new( @@ -573,17 +566,13 @@ impl TryFrom .map(|entry| { let key = entry .key - .ok_or(ConversionError::missing_field::( - "key", - ))? + .ok_or(ConversionError::missing_field::("key"))? .try_into() .map(StorageMapKey::new) .context("key")?; let value = entry .value - .ok_or(ConversionError::missing_field::( - "value", - ))? + .ok_or(ConversionError::missing_field::("value"))? .try_into() .context("value")?; Ok((key, value)) @@ -701,12 +690,8 @@ impl TryFrom for AccountStorageDetails { fn try_from(value: proto::rpc::AccountStorageDetails) -> Result { let proto::rpc::AccountStorageDetails { header, map_details } = value; - let header = header - .ok_or(ConversionError::missing_field::( - "header", - ))? - .try_into() - .context("header")?; + let header = + try_convert_field::(header, "header")?; let map_details = try_convert(map_details).collect::, _>>().context("map_details")?; @@ -761,10 +746,7 @@ impl TryFrom for AccountResponse { .ok_or(ConversionError::missing_field::("block_num"))? .into(); - let witness = witness - .ok_or(ConversionError::missing_field::("witness"))? - .try_into() - .context("witness")?; + let witness = try_convert_field::(witness, "witness")?; let details = details.map(TryFrom::try_from).transpose().context("details")?; @@ -824,26 +806,20 @@ impl TryFrom for AccountDetails { storage_details, } = value; - let account_header = header - .ok_or(ConversionError::missing_field::( - "header", - ))? - .try_into() - .context("header")?; + let account_header = try_convert_field::( + header, "header", + )?; - let storage_details = storage_details - .ok_or(ConversionError::missing_field::( - "storage_details", - ))? - .try_into() - .context("storage_details")?; + let storage_details = try_convert_field::< + proto::rpc::account_response::AccountDetails, + _, + _, + >(storage_details, "storage_details")?; - let vault_details = vault_details - .ok_or(ConversionError::missing_field::( - "vault_details", - ))? - .try_into() - .context("vault_details")?; + let vault_details = try_convert_field::( + vault_details, + "vault_details", + )?; let account_code = code; Ok(AccountDetails { @@ -885,21 +861,18 @@ impl TryFrom for AccountWitness { type Error = ConversionError; fn try_from(account_witness: proto::account::AccountWitness) -> Result { - let witness_id = account_witness - .witness_id - .ok_or(ConversionError::missing_field::("witness_id"))? - .try_into() - .context("witness_id")?; - let commitment = account_witness - .commitment - .ok_or(ConversionError::missing_field::("commitment"))? - .try_into() - .context("commitment")?; - let path = account_witness - .path - .ok_or(ConversionError::missing_field::("path"))? - .try_into() - .context("path")?; + let witness_id = try_convert_field::( + account_witness.witness_id, + "witness_id", + )?; + let commitment = try_convert_field::( + account_witness.commitment, + "commitment", + )?; + let path = try_convert_field::( + account_witness.path, + "path", + )?; AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -936,16 +909,14 @@ impl TryFrom for AccountWitnessRecord { fn try_from( account_witness_record: proto::account::AccountWitness, ) -> Result { - let witness_id = account_witness_record - .witness_id - .ok_or(ConversionError::missing_field::("witness_id"))? - .try_into() - .context("witness_id")?; - let commitment = account_witness_record - .commitment - .ok_or(ConversionError::missing_field::("commitment"))? - .try_into() - .context("commitment")?; + let witness_id = try_convert_field::( + account_witness_record.witness_id, + "witness_id", + )?; + let commitment = try_convert_field::( + account_witness_record.commitment, + "commitment", + )?; let path: SparseMerklePath = account_witness_record .path .as_ref() @@ -962,13 +933,10 @@ impl TryFrom for AccountWitnessRecord { })?; Ok(Self { - account_id: account_witness_record - .account_id - .ok_or(ConversionError::missing_field::( - "account_id", - ))? - .try_into() - .context("account_id")?, + account_id: try_convert_field::( + account_witness_record.account_id, + "account_id", + )?, witness, }) } @@ -1013,21 +981,17 @@ impl TryFrom fo fn try_from( from: proto::store::transaction_inputs::AccountTransactionInputRecord, ) -> Result { - let account_id = from - .account_id - .ok_or(ConversionError::missing_field::< - proto::store::transaction_inputs::AccountTransactionInputRecord, - >("account_id"))? - .try_into() - .context("account_id")?; - - let account_commitment = from - .account_commitment - .ok_or(ConversionError::missing_field::< - proto::store::transaction_inputs::AccountTransactionInputRecord, - >("account_commitment"))? - .try_into() - .context("account_commitment")?; + let account_id = try_convert_field::< + proto::store::transaction_inputs::AccountTransactionInputRecord, + _, + _, + >(from.account_id, "account_id")?; + + let account_commitment = try_convert_field::< + proto::store::transaction_inputs::AccountTransactionInputRecord, + _, + _, + >(from.account_commitment, "account_commitment")?; // If the commitment is equal to `Word::empty()`, it signifies that this is a new // account which is not yet present in the Store. diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index af30b8a157..a64f3f9cbf 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -5,7 +5,7 @@ use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; use crate::generated as proto; /// Data required for a transaction batch. @@ -31,11 +31,10 @@ impl TryFrom for BatchInputs { fn try_from(response: proto::store::BatchInputs) -> Result { let result = Self { - batch_reference_block_header: response - .batch_reference_block_header - .ok_or(ConversionError::missing_field::("block_header"))? - .try_into() - .context("batch_reference_block_header")?, + batch_reference_block_header: try_convert_field::( + response.batch_reference_block_header, + "block_header", + )?, note_proofs: response .note_proofs .iter() diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index a621b5227c..22f2436959 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -17,7 +17,7 @@ use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; use thiserror::Error; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; // BLOCK NUMBER @@ -75,69 +75,55 @@ impl TryFrom for BlockHeader { type Error = ConversionError; fn try_from(value: proto::blockchain::BlockHeader) -> Result { + let prev_block_commitment = try_convert_field::( + value.prev_block_commitment, + "prev_block_commitment", + )?; + let chain_commitment = try_convert_field::( + value.chain_commitment, + "chain_commitment", + )?; + let account_root = try_convert_field::( + value.account_root, + "account_root", + )?; + let nullifier_root = try_convert_field::( + value.nullifier_root, + "nullifier_root", + )?; + let note_root = try_convert_field::( + value.note_root, + "note_root", + )?; + let tx_commitment = try_convert_field::( + value.tx_commitment, + "tx_commitment", + )?; + let tx_kernel_commitment = try_convert_field::( + value.tx_kernel_commitment, + "tx_kernel_commitment", + )?; + let validator_key = try_convert_field::( + value.validator_key, + "validator_key", + )?; + let fee_parameters = try_convert_field::( + value.fee_parameters, + "fee_parameters", + )?; + Ok(BlockHeader::new( value.version, - value - .prev_block_commitment - .ok_or(ConversionError::missing_field::( - "prev_block_commitment", - ))? - .try_into() - .context("prev_block_commitment")?, + prev_block_commitment, value.block_num.into(), - value - .chain_commitment - .ok_or(ConversionError::missing_field::( - "chain_commitment", - ))? - .try_into() - .context("chain_commitment")?, - value - .account_root - .ok_or(ConversionError::missing_field::( - "account_root", - ))? - .try_into() - .context("account_root")?, - value - .nullifier_root - .ok_or(ConversionError::missing_field::( - "nullifier_root", - ))? - .try_into() - .context("nullifier_root")?, - value - .note_root - .ok_or(ConversionError::missing_field::( - "note_root", - ))? - .try_into() - .context("note_root")?, - value - .tx_commitment - .ok_or(ConversionError::missing_field::( - "tx_commitment", - ))? - .try_into() - .context("tx_commitment")?, - value - .tx_kernel_commitment - .ok_or(ConversionError::missing_field::( - "tx_kernel_commitment", - ))? - .try_into() - .context("tx_kernel_commitment")?, - value - .validator_key - .ok_or(ConversionError::missing_field::( - "validator_key", - ))? - .try_into() - .context("validator_key")?, - FeeParameters::try_from(value.fee_parameters.ok_or( - ConversionError::missing_field::("fee_parameters"), - )?) - .context("fee_parameters")?, + chain_commitment, + account_root, + nullifier_root, + note_root, + tx_commitment, + tx_kernel_commitment, + validator_key, + fee_parameters, value.timestamp, )) } @@ -204,21 +190,13 @@ impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { impl TryFrom for SignedBlock { type Error = ConversionError; fn try_from(value: proto::blockchain::SignedBlock) -> Result { - let header = value - .header - .ok_or(ConversionError::missing_field::("header"))? - .try_into() - .context("header")?; - let body = value - .body - .ok_or(ConversionError::missing_field::("body"))? - .try_into() - .context("body")?; - let signature = value - .signature - .ok_or(ConversionError::missing_field::("signature"))? - .try_into() - .context("signature")?; + let header = + try_convert_field::(value.header, "header")?; + let body = try_convert_field::(value.body, "body")?; + let signature = try_convert_field::( + value.signature, + "signature", + )?; Ok(SignedBlock::new_unchecked(header, body, signature)) } @@ -263,13 +241,10 @@ impl TryFrom for BlockInputs { type Error = ConversionError; fn try_from(response: proto::store::BlockInputs) -> Result { - let latest_block_header: BlockHeader = response - .latest_block_header - .ok_or(ConversionError::missing_field::( - "latest_block_header", - ))? - .try_into() - .context("latest_block_header")?; + let latest_block_header: BlockHeader = try_convert_field::( + response.latest_block_header, + "latest_block_header", + )?; let account_witnesses = response .account_witnesses diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index 32fdb5b7f8..413f98975f 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -7,7 +7,7 @@ use miden_protocol::transaction::TransactionId; use miden_protocol::utils::{Deserializable, Serializable}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; use crate::generated as proto; #[derive(Debug, Clone)] @@ -88,13 +88,11 @@ impl TryFrom for MempoolEvent { match event { proto::block_producer::mempool_event::Event::TransactionAdded(tx) => { - let id = tx - .id - .ok_or(ConversionError::missing_field::< - proto::block_producer::mempool_event::TransactionAdded, - >("id"))? - .try_into() - .context("id")?; + let id = try_convert_field::< + proto::block_producer::mempool_event::TransactionAdded, + _, + _, + >(tx.id, "id")?; let nullifiers = tx .nullifiers .into_iter() @@ -122,13 +120,11 @@ impl TryFrom for MempoolEvent { }) }, proto::block_producer::mempool_event::Event::BlockCommitted(block_committed) => { - let header = block_committed - .block_header - .ok_or(ConversionError::missing_field::< - proto::block_producer::mempool_event::BlockCommitted, - >("block_header"))? - .try_into() - .context("block_header")?; + let header = try_convert_field::< + proto::block_producer::mempool_event::BlockCommitted, + _, + _, + >(block_committed.block_header, "block_header")?; let header = Box::new(header); let txs = block_committed .transactions diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index cb33d92ebb..1ae49ff24b 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -4,7 +4,7 @@ use miden_protocol::crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath}; use crate::domain::{convert, try_convert}; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; use crate::generated as proto; // MERKLE PATH @@ -109,9 +109,9 @@ impl TryFrom for SmtLeaf { type Error = ConversionError; fn try_from(value: proto::primitives::SmtLeaf) -> Result { - let leaf = value.leaf.ok_or( - ConversionError::missing_field::("leaf"), - )?; + let leaf = value + .leaf + .ok_or(ConversionError::missing_field::("leaf"))?; match leaf { proto::primitives::smt_leaf::Leaf::EmptyLeafIndex(leaf_index) => { @@ -155,16 +155,10 @@ impl TryFrom for (Word, Word) { type Error = ConversionError; fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { - let key: Word = entry - .key - .ok_or(ConversionError::missing_field::("key"))? - .try_into() - .context("key")?; - let value: Word = entry - .value - .ok_or(ConversionError::missing_field::("value"))? - .try_into() - .context("value")?; + let key: Word = + try_convert_field::(entry.key, "key")?; + let value: Word = + try_convert_field::(entry.value, "value")?; Ok((key, value)) } @@ -186,16 +180,10 @@ impl TryFrom for SmtProof { type Error = ConversionError; fn try_from(opening: proto::primitives::SmtOpening) -> Result { - let path: SparseMerklePath = opening - .path - .ok_or(ConversionError::missing_field::("path"))? - .try_into() - .context("path")?; - let leaf: SmtLeaf = opening - .leaf - .ok_or(ConversionError::missing_field::("leaf"))? - .try_into() - .context("leaf")?; + let path: SparseMerklePath = + try_convert_field::(opening.path, "path")?; + let leaf: SmtLeaf = + try_convert_field::(opening.leaf, "leaf")?; Ok(SmtProof::new(path, leaf)?) } diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 202ead1321..21440d4f1f 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -17,7 +17,7 @@ use miden_protocol::utils::{Deserializable, Serializable}; use miden_protocol::{MastForest, MastNodeId, Word}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; use crate::generated as proto; // NOTE TYPE @@ -53,13 +53,7 @@ impl TryFrom for NoteMetadata { type Error = ConversionError; fn try_from(value: proto::note::NoteMetadata) -> Result { - let sender = value - .sender - .ok_or_else(|| { - ConversionError::missing_field::("sender") - })? - .try_into() - .context("sender")?; + let sender = try_convert_field::(value.sender, "sender")?; let note_type = proto::note::NoteType::try_from(value.note_type) .map_err(|_| ConversionError::message("enum variant discriminant out of range"))? .try_into() @@ -113,13 +107,8 @@ impl TryFrom for AccountTargetNetworkNote { let details = NoteDetails::read_from_bytes(&value.details) .map_err(|err| ConversionError::deserialization("NoteDetails", err))?; let (assets, recipient) = details.into_parts(); - let metadata: NoteMetadata = value - .metadata - .ok_or_else(|| { - ConversionError::missing_field::("metadata") - })? - .try_into() - .context("metadata")?; + let metadata: NoteMetadata = + try_convert_field::(value.metadata, "metadata")?; let note = Note::new(assets, metadata, recipient); AccountTargetNetworkNote::new(note).map_err(ConversionError::from) } @@ -216,11 +205,8 @@ impl TryFrom for Note { type Error = ConversionError; fn try_from(proto_note: proto::note::Note) -> Result { - let metadata: NoteMetadata = proto_note - .metadata - .ok_or(ConversionError::missing_field::("metadata"))? - .try_into() - .context("metadata")?; + let metadata: NoteMetadata = + try_convert_field::(proto_note.metadata, "metadata")?; let details = proto_note .details @@ -250,20 +236,10 @@ impl TryFrom for NoteHeader { type Error = ConversionError; fn try_from(value: proto::note::NoteHeader) -> Result { - let note_id_word: Word = value - .note_id - .ok_or_else(|| { - ConversionError::missing_field::("note_id") - })? - .try_into() - .context("note_id")?; - let metadata: NoteMetadata = value - .metadata - .ok_or_else(|| { - ConversionError::missing_field::("metadata") - })? - .try_into() - .context("metadata")?; + let note_id_word: Word = + try_convert_field::(value.note_id, "note_id")?; + let metadata: NoteMetadata = + try_convert_field::(value.metadata, "metadata")?; Ok(NoteHeader::new(NoteId::from_raw(note_id_word), metadata)) } diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index 2767fb8a06..f3c516dbdd 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -2,7 +2,7 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::smt::SmtProof; use miden_protocol::note::Nullifier; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, try_convert_field}; use crate::generated as proto; // FROM NULLIFIER @@ -48,20 +48,14 @@ impl TryFrom for NullifierWitnessR nullifier_witness_record: proto::store::block_inputs::NullifierWitness, ) -> Result { Ok(Self { - nullifier: nullifier_witness_record - .nullifier - .ok_or(ConversionError::missing_field::< - proto::store::block_inputs::NullifierWitness, - >("nullifier"))? - .try_into() - .context("nullifier")?, - proof: nullifier_witness_record - .opening - .ok_or(ConversionError::missing_field::< - proto::store::block_inputs::NullifierWitness, - >("opening"))? - .try_into() - .context("opening")?, + nullifier: try_convert_field::( + nullifier_witness_record.nullifier, + "nullifier", + )?, + proof: try_convert_field::( + nullifier_witness_record.opening, + "opening", + )?, }) } } diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index 8536857a4e..ca5b80b442 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -3,7 +3,7 @@ use miden_protocol::note::Nullifier; use miden_protocol::transaction::{InputNoteCommitment, TransactionId}; use miden_protocol::utils::{Deserializable, Serializable}; -use crate::errors::{ConversionError, ConversionResultExt}; +use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; use crate::generated as proto; // FROM TRANSACTION ID @@ -49,11 +49,7 @@ impl TryFrom for TransactionId { type Error = ConversionError; fn try_from(value: proto::transaction::TransactionId) -> Result { - value - .id - .ok_or(ConversionError::missing_field::("id"))? - .try_into() - .context("id") + try_convert_field::(value.id, "id") } } @@ -73,15 +69,11 @@ impl TryFrom for InputNoteCommitment { type Error = ConversionError; fn try_from(value: proto::transaction::InputNoteCommitment) -> Result { - let nullifier: Nullifier = value - .nullifier - .ok_or_else(|| { - ConversionError::missing_field::( - "nullifier", - ) - })? - .try_into() - .context("nullifier")?; + let nullifier: Nullifier = try_convert_field::< + proto::transaction::InputNoteCommitment, + _, + _, + >(value.nullifier, "nullifier")?; let header: Option = value.header.map(TryInto::try_into).transpose().context("header")?; diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index a005d1ad99..278fc7504a 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -173,6 +173,38 @@ impl> ConversionResultExt for Result { } } +// FIELD CONVERSION HELPERS +// ================================================================================================ + +/// Converts a required `Option` field from a protobuf message, combining the missing-field check +/// and fallible conversion into a single call. +/// +/// This deduplicates the common pattern of: +/// ```rust,ignore +/// let body = block.body +/// .ok_or(ConversionError::missing_field::("body"))? +/// .try_into() +/// .context("body")?; +/// ``` +/// +/// Into: +/// ```rust,ignore +/// let body = try_convert_field::(block.body, "body")?; +/// ``` +pub fn try_convert_field( + field: Option, + name: &'static str, +) -> Result +where + F: TryInto, + F::Error: Into, +{ + field + .ok_or_else(|| ConversionError::missing_field::(name))? + .try_into() + .context(name) +} + // FROM IMPLS FOR EXTERNAL ERROR TYPES // ================================================================================================ @@ -250,4 +282,42 @@ mod tests { let err = result.context("fee_amount").unwrap_err(); assert!(err.to_string().starts_with("fee_amount: "), "expected field prefix, got: {err}",); } + + #[test] + fn test_try_convert_field_missing() { + use miden_protocol::Felt; + + use crate::generated::primitives::Digest; + + let result = try_convert_field::< + crate::generated::blockchain::BlockHeader, + [Felt; 4], + Digest, + >(None, "account_root"); + let err = result.unwrap_err(); + assert!( + err.to_string().contains("account_root") && err.to_string().contains("missing"), + "expected missing field error, got: {err}", + ); + } + + #[test] + fn test_try_convert_field_conversion_error() { + use miden_protocol::Felt; + + use crate::generated::primitives::Digest; + + // Create a digest with an out-of-range value. + let bad_digest = Digest { d0: u64::MAX, d1: 0, d2: 0, d3: 0 }; + let result = try_convert_field::< + crate::generated::blockchain::BlockHeader, + [Felt; 4], + Digest, + >(Some(bad_digest), "account_root"); + let err = result.unwrap_err(); + assert!( + err.to_string().starts_with("account_root: "), + "expected field prefix, got: {err}", + ); + } } diff --git a/crates/store/src/server/block_producer.rs b/crates/store/src/server/block_producer.rs index 73c94923c5..f8a5527399 100644 --- a/crates/store/src/server/block_producer.rs +++ b/crates/store/src/server/block_producer.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; use futures::TryFutureExt; use miden_crypto::dsa::ecdsa_k256_keccak::Signature; -use miden_node_proto::errors::{ConversionError, ConversionResultExt}; +use miden_node_proto::errors::{ConversionError, try_convert_field}; use miden_node_proto::generated::store::block_producer_server; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -59,23 +59,16 @@ impl block_producer_server::BlockProducer for StoreApi { .block .ok_or(ConversionError::missing_field::("block"))?; // Read block header. - let header: BlockHeader = block - .header - .ok_or(ConversionError::missing_field::("header"))? - .try_into() - .context("header")?; + let header: BlockHeader = + try_convert_field::(block.header, "header")?; // Read block body. - let body: BlockBody = block - .body - .ok_or(ConversionError::missing_field::("body"))? - .try_into() - .context("body")?; + let body: BlockBody = + try_convert_field::(block.body, "body")?; // Read signature. - let signature: Signature = block - .signature - .ok_or(ConversionError::missing_field::("signature"))? - .try_into() - .context("signature")?; + let signature: Signature = try_convert_field::( + block.signature, + "signature", + )?; // Get block inputs from ordered batches. let block_inputs = From d3ce53c807202cdc492c567ef6f528f6af7d385d Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 14:24:19 +1300 Subject: [PATCH 17/26] TryConvertFieldExt --- crates/block-producer/src/store/mod.rs | 15 ++- crates/proto/src/domain/account.rs | 113 +++++++++------------- crates/proto/src/domain/batch.rs | 7 +- crates/proto/src/domain/block.rs | 85 +++++++--------- crates/proto/src/domain/mempool.rs | 22 ++--- crates/proto/src/domain/merkle.rs | 11 +-- crates/proto/src/domain/note.rs | 12 +-- crates/proto/src/domain/nullifier.rs | 14 +-- crates/proto/src/domain/transaction.rs | 12 +-- crates/proto/src/errors/mod.rs | 60 ++++++------ crates/store/src/server/block_producer.rs | 13 ++- 11 files changed, 167 insertions(+), 197 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index a9bb9dcb7c..3431b40703 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -5,7 +5,7 @@ use std::num::NonZeroU32; use itertools::Itertools; use miden_node_proto::clients::{Builder, StoreBlockProducerClient}; use miden_node_proto::domain::batch::BatchInputs; -use miden_node_proto::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use miden_node_proto::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use miden_node_proto::{AccountState, generated as proto}; use miden_node_utils::formatting::format_opt; use miden_protocol::Word; @@ -71,18 +71,15 @@ impl TryFrom for TransactionInputs { fn try_from(response: proto::store::TransactionInputs) -> Result { let AccountState { account_id, account_commitment } = - try_convert_field::( - response.account_state, - "account_state", - )?; + response + .account_state + .try_convert_field::("account_state")?; let mut nullifiers = HashMap::new(); for nullifier_record in response.nullifiers { - let nullifier = try_convert_field::< + let nullifier = nullifier_record.nullifier.try_convert_field::< proto::store::transaction_inputs::NullifierTransactionInputRecord, - _, - _, - >(nullifier_record.nullifier, "nullifier")?; + >("nullifier")?; // Note that this intentionally maps 0 to None as this is the definition used in // protobuf. diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 3f7f8ce471..dd0e839111 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -25,7 +25,7 @@ use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError}; use thiserror::Error; use super::try_convert; -use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use crate::generated::{self as proto}; #[cfg(test)] @@ -158,7 +158,7 @@ impl TryFrom for AccountRequest { let proto::rpc::AccountRequest { account_id, block_num, details } = value; let account_id = - try_convert_field::(account_id, "account_id")?; + account_id.try_convert_field::("account_id")?; let block_num = block_num.map(Into::into); let details = details.map(TryFrom::try_from).transpose().context("details")?; @@ -223,11 +223,9 @@ impl TryFrom(slot_data, "slot_data")?; + >("slot_data")?; Ok(StorageMapRequest { slot_name, slot_data }) } @@ -281,17 +279,13 @@ impl TryFrom for AccountHeader { } = value; let account_id = - try_convert_field::(account_id, "account_id")?; + account_id.try_convert_field::("account_id")?; let vault_root = - try_convert_field::(vault_root, "vault_root")?; - let storage_commitment = try_convert_field::( - storage_commitment, - "storage_commitment", - )?; - let code_commitment = try_convert_field::( - code_commitment, - "code_commitment", - )?; + vault_root.try_convert_field::("vault_root")?; + let storage_commitment = storage_commitment + .try_convert_field::("storage_commitment")?; + let code_commitment = code_commitment + .try_convert_field::("code_commitment")?; let nonce = nonce.try_into().map_err(ConversionError::message).context("nonce")?; Ok(AccountHeader::new( @@ -690,8 +684,7 @@ impl TryFrom for AccountStorageDetails { fn try_from(value: proto::rpc::AccountStorageDetails) -> Result { let proto::rpc::AccountStorageDetails { header, map_details } = value; - let header = - try_convert_field::(header, "header")?; + let header = header.try_convert_field::("header")?; let map_details = try_convert(map_details).collect::, _>>().context("map_details")?; @@ -746,7 +739,7 @@ impl TryFrom for AccountResponse { .ok_or(ConversionError::missing_field::("block_num"))? .into(); - let witness = try_convert_field::(witness, "witness")?; + let witness = witness.try_convert_field::("witness")?; let details = details.map(TryFrom::try_from).transpose().context("details")?; @@ -806,20 +799,14 @@ impl TryFrom for AccountDetails { storage_details, } = value; - let account_header = try_convert_field::( - header, "header", - )?; + let account_header = + header.try_convert_field::("header")?; - let storage_details = try_convert_field::< - proto::rpc::account_response::AccountDetails, - _, - _, - >(storage_details, "storage_details")?; + let storage_details = storage_details + .try_convert_field::("storage_details")?; - let vault_details = try_convert_field::( - vault_details, - "vault_details", - )?; + let vault_details = vault_details + .try_convert_field::("vault_details")?; let account_code = code; Ok(AccountDetails { @@ -861,18 +848,15 @@ impl TryFrom for AccountWitness { type Error = ConversionError; fn try_from(account_witness: proto::account::AccountWitness) -> Result { - let witness_id = try_convert_field::( - account_witness.witness_id, - "witness_id", - )?; - let commitment = try_convert_field::( - account_witness.commitment, - "commitment", - )?; - let path = try_convert_field::( - account_witness.path, - "path", - )?; + let witness_id = account_witness + .witness_id + .try_convert_field::("witness_id")?; + let commitment = account_witness + .commitment + .try_convert_field::("commitment")?; + let path = account_witness + .path + .try_convert_field::("path")?; AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -909,14 +893,12 @@ impl TryFrom for AccountWitnessRecord { fn try_from( account_witness_record: proto::account::AccountWitness, ) -> Result { - let witness_id = try_convert_field::( - account_witness_record.witness_id, - "witness_id", - )?; - let commitment = try_convert_field::( - account_witness_record.commitment, - "commitment", - )?; + let witness_id = account_witness_record + .witness_id + .try_convert_field::("witness_id")?; + let commitment = account_witness_record + .commitment + .try_convert_field::("commitment")?; let path: SparseMerklePath = account_witness_record .path .as_ref() @@ -933,10 +915,9 @@ impl TryFrom for AccountWitnessRecord { })?; Ok(Self { - account_id: try_convert_field::( - account_witness_record.account_id, - "account_id", - )?, + account_id: account_witness_record + .account_id + .try_convert_field::("account_id")?, witness, }) } @@ -981,17 +962,17 @@ impl TryFrom fo fn try_from( from: proto::store::transaction_inputs::AccountTransactionInputRecord, ) -> Result { - let account_id = try_convert_field::< - proto::store::transaction_inputs::AccountTransactionInputRecord, - _, - _, - >(from.account_id, "account_id")?; - - let account_commitment = try_convert_field::< - proto::store::transaction_inputs::AccountTransactionInputRecord, - _, - _, - >(from.account_commitment, "account_commitment")?; + let account_id = from + .account_id + .try_convert_field::( + "account_id", + )?; + + let account_commitment = from + .account_commitment + .try_convert_field::( + "account_commitment", + )?; // If the commitment is equal to `Word::empty()`, it signifies that this is a new // account which is not yet present in the Store. diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index a64f3f9cbf..8b6ee62a3d 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -5,7 +5,7 @@ use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; -use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use crate::generated as proto; /// Data required for a transaction batch. @@ -31,8 +31,9 @@ impl TryFrom for BatchInputs { fn try_from(response: proto::store::BatchInputs) -> Result { let result = Self { - batch_reference_block_header: try_convert_field::( - response.batch_reference_block_header, + batch_reference_block_header: response + .batch_reference_block_header + .try_convert_field::( "block_header", )?, note_proofs: response diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 22f2436959..943b399168 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -17,7 +17,7 @@ use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::{Deserializable, Serializable}; use thiserror::Error; -use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; // BLOCK NUMBER @@ -75,42 +75,33 @@ impl TryFrom for BlockHeader { type Error = ConversionError; fn try_from(value: proto::blockchain::BlockHeader) -> Result { - let prev_block_commitment = try_convert_field::( - value.prev_block_commitment, - "prev_block_commitment", - )?; - let chain_commitment = try_convert_field::( - value.chain_commitment, - "chain_commitment", - )?; - let account_root = try_convert_field::( - value.account_root, - "account_root", - )?; - let nullifier_root = try_convert_field::( - value.nullifier_root, - "nullifier_root", - )?; - let note_root = try_convert_field::( - value.note_root, - "note_root", - )?; - let tx_commitment = try_convert_field::( - value.tx_commitment, - "tx_commitment", - )?; - let tx_kernel_commitment = try_convert_field::( - value.tx_kernel_commitment, - "tx_kernel_commitment", - )?; - let validator_key = try_convert_field::( - value.validator_key, - "validator_key", - )?; - let fee_parameters = try_convert_field::( - value.fee_parameters, - "fee_parameters", - )?; + let prev_block_commitment = value + .prev_block_commitment + .try_convert_field::("prev_block_commitment")?; + let chain_commitment = value + .chain_commitment + .try_convert_field::("chain_commitment")?; + let account_root = value + .account_root + .try_convert_field::("account_root")?; + let nullifier_root = value + .nullifier_root + .try_convert_field::("nullifier_root")?; + let note_root = value + .note_root + .try_convert_field::("note_root")?; + let tx_commitment = value + .tx_commitment + .try_convert_field::("tx_commitment")?; + let tx_kernel_commitment = value + .tx_kernel_commitment + .try_convert_field::("tx_kernel_commitment")?; + let validator_key = value + .validator_key + .try_convert_field::("validator_key")?; + let fee_parameters = value + .fee_parameters + .try_convert_field::("fee_parameters")?; Ok(BlockHeader::new( value.version, @@ -190,13 +181,11 @@ impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { impl TryFrom for SignedBlock { type Error = ConversionError; fn try_from(value: proto::blockchain::SignedBlock) -> Result { - let header = - try_convert_field::(value.header, "header")?; - let body = try_convert_field::(value.body, "body")?; - let signature = try_convert_field::( - value.signature, - "signature", - )?; + let header = value.header.try_convert_field::("header")?; + let body = value.body.try_convert_field::("body")?; + let signature = value + .signature + .try_convert_field::("signature")?; Ok(SignedBlock::new_unchecked(header, body, signature)) } @@ -241,10 +230,10 @@ impl TryFrom for BlockInputs { type Error = ConversionError; fn try_from(response: proto::store::BlockInputs) -> Result { - let latest_block_header: BlockHeader = try_convert_field::( - response.latest_block_header, - "latest_block_header", - )?; + let latest_block_header: BlockHeader = + response + .latest_block_header + .try_convert_field::("latest_block_header")?; let account_witnesses = response .account_witnesses diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index 413f98975f..b3181c3002 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -7,7 +7,7 @@ use miden_protocol::transaction::TransactionId; use miden_protocol::utils::{Deserializable, Serializable}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use crate::generated as proto; #[derive(Debug, Clone)] @@ -88,11 +88,11 @@ impl TryFrom for MempoolEvent { match event { proto::block_producer::mempool_event::Event::TransactionAdded(tx) => { - let id = try_convert_field::< - proto::block_producer::mempool_event::TransactionAdded, - _, - _, - >(tx.id, "id")?; + let id = tx + .id + .try_convert_field::( + "id", + )?; let nullifiers = tx .nullifiers .into_iter() @@ -120,11 +120,11 @@ impl TryFrom for MempoolEvent { }) }, proto::block_producer::mempool_event::Event::BlockCommitted(block_committed) => { - let header = try_convert_field::< - proto::block_producer::mempool_event::BlockCommitted, - _, - _, - >(block_committed.block_header, "block_header")?; + let header = block_committed + .block_header + .try_convert_field::( + "block_header", + )?; let header = Box::new(header); let txs = block_committed .transactions diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index 1ae49ff24b..cd735dc74b 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -4,7 +4,7 @@ use miden_protocol::crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath}; use crate::domain::{convert, try_convert}; -use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use crate::generated as proto; // MERKLE PATH @@ -155,10 +155,9 @@ impl TryFrom for (Word, Word) { type Error = ConversionError; fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { - let key: Word = - try_convert_field::(entry.key, "key")?; + let key: Word = entry.key.try_convert_field::("key")?; let value: Word = - try_convert_field::(entry.value, "value")?; + entry.value.try_convert_field::("value")?; Ok((key, value)) } @@ -181,9 +180,9 @@ impl TryFrom for SmtProof { fn try_from(opening: proto::primitives::SmtOpening) -> Result { let path: SparseMerklePath = - try_convert_field::(opening.path, "path")?; + opening.path.try_convert_field::("path")?; let leaf: SmtLeaf = - try_convert_field::(opening.leaf, "leaf")?; + opening.leaf.try_convert_field::("leaf")?; Ok(SmtProof::new(path, leaf)?) } diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 21440d4f1f..0ad2b4a383 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -17,7 +17,7 @@ use miden_protocol::utils::{Deserializable, Serializable}; use miden_protocol::{MastForest, MastNodeId, Word}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use crate::generated as proto; // NOTE TYPE @@ -53,7 +53,7 @@ impl TryFrom for NoteMetadata { type Error = ConversionError; fn try_from(value: proto::note::NoteMetadata) -> Result { - let sender = try_convert_field::(value.sender, "sender")?; + let sender = value.sender.try_convert_field::("sender")?; let note_type = proto::note::NoteType::try_from(value.note_type) .map_err(|_| ConversionError::message("enum variant discriminant out of range"))? .try_into() @@ -108,7 +108,7 @@ impl TryFrom for AccountTargetNetworkNote { .map_err(|err| ConversionError::deserialization("NoteDetails", err))?; let (assets, recipient) = details.into_parts(); let metadata: NoteMetadata = - try_convert_field::(value.metadata, "metadata")?; + value.metadata.try_convert_field::("metadata")?; let note = Note::new(assets, metadata, recipient); AccountTargetNetworkNote::new(note).map_err(ConversionError::from) } @@ -206,7 +206,7 @@ impl TryFrom for Note { fn try_from(proto_note: proto::note::Note) -> Result { let metadata: NoteMetadata = - try_convert_field::(proto_note.metadata, "metadata")?; + proto_note.metadata.try_convert_field::("metadata")?; let details = proto_note .details @@ -237,9 +237,9 @@ impl TryFrom for NoteHeader { fn try_from(value: proto::note::NoteHeader) -> Result { let note_id_word: Word = - try_convert_field::(value.note_id, "note_id")?; + value.note_id.try_convert_field::("note_id")?; let metadata: NoteMetadata = - try_convert_field::(value.metadata, "metadata")?; + value.metadata.try_convert_field::("metadata")?; Ok(NoteHeader::new(NoteId::from_raw(note_id_word), metadata)) } diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index f3c516dbdd..b1669126f9 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -2,7 +2,7 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::smt::SmtProof; use miden_protocol::note::Nullifier; -use crate::errors::{ConversionError, try_convert_field}; +use crate::errors::{ConversionError, TryConvertFieldExt}; use crate::generated as proto; // FROM NULLIFIER @@ -48,14 +48,14 @@ impl TryFrom for NullifierWitnessR nullifier_witness_record: proto::store::block_inputs::NullifierWitness, ) -> Result { Ok(Self { - nullifier: try_convert_field::( - nullifier_witness_record.nullifier, + nullifier: nullifier_witness_record + .nullifier + .try_convert_field::( "nullifier", )?, - proof: try_convert_field::( - nullifier_witness_record.opening, - "opening", - )?, + proof: nullifier_witness_record + .opening + .try_convert_field::("opening")?, }) } } diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index ca5b80b442..b7fe668f50 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -3,7 +3,7 @@ use miden_protocol::note::Nullifier; use miden_protocol::transaction::{InputNoteCommitment, TransactionId}; use miden_protocol::utils::{Deserializable, Serializable}; -use crate::errors::{ConversionError, ConversionResultExt, try_convert_field}; +use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use crate::generated as proto; // FROM TRANSACTION ID @@ -49,7 +49,7 @@ impl TryFrom for TransactionId { type Error = ConversionError; fn try_from(value: proto::transaction::TransactionId) -> Result { - try_convert_field::(value.id, "id") + value.id.try_convert_field::("id") } } @@ -69,11 +69,9 @@ impl TryFrom for InputNoteCommitment { type Error = ConversionError; fn try_from(value: proto::transaction::InputNoteCommitment) -> Result { - let nullifier: Nullifier = try_convert_field::< - proto::transaction::InputNoteCommitment, - _, - _, - >(value.nullifier, "nullifier")?; + let nullifier: Nullifier = value + .nullifier + .try_convert_field::("nullifier")?; let header: Option = value.header.map(TryInto::try_into).transpose().context("header")?; diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 278fc7504a..556c99d377 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -176,33 +176,42 @@ impl> ConversionResultExt for Result { // FIELD CONVERSION HELPERS // ================================================================================================ -/// Converts a required `Option` field from a protobuf message, combining the missing-field check -/// and fallible conversion into a single call. +/// Extension trait on `Option` to convert a required protobuf field in one step. +/// +/// Combines the missing-field check, fallible conversion, and context injection: /// -/// This deduplicates the common pattern of: /// ```rust,ignore +/// // Before: /// let body = block.body /// .ok_or(ConversionError::missing_field::("body"))? /// .try_into() /// .context("body")?; -/// ``` /// -/// Into: -/// ```rust,ignore -/// let body = try_convert_field::(block.body, "body")?; +/// // After: +/// let body = block.body.try_convert_field::("body")?; /// ``` -pub fn try_convert_field( - field: Option, - name: &'static str, -) -> Result +pub trait TryConvertFieldExt { + /// Unwraps the `Option`, returning a [`ConversionError::missing_field`] for `None`, + /// then converts via `TryInto` with field context on failure. + /// + /// `M` is the parent protobuf message that contains this field. + fn try_convert_field(self, name: &'static str) + -> Result; +} + +impl TryConvertFieldExt for Option where - F: TryInto, - F::Error: Into, + T: TryInto, + T::Error: Into, { - field - .ok_or_else(|| ConversionError::missing_field::(name))? - .try_into() - .context(name) + fn try_convert_field( + self, + name: &'static str, + ) -> Result { + self.ok_or_else(|| ConversionError::missing_field::(name))? + .try_into() + .context(name) + } } // FROM IMPLS FOR EXTERNAL ERROR TYPES @@ -289,11 +298,10 @@ mod tests { use crate::generated::primitives::Digest; - let result = try_convert_field::< - crate::generated::blockchain::BlockHeader, - [Felt; 4], - Digest, - >(None, "account_root"); + let field: Option = None; + let result = + field.try_convert_field::("account_root"); + let result: Result<[Felt; 4], _> = result; let err = result.unwrap_err(); assert!( err.to_string().contains("account_root") && err.to_string().contains("missing"), @@ -309,11 +317,9 @@ mod tests { // Create a digest with an out-of-range value. let bad_digest = Digest { d0: u64::MAX, d1: 0, d2: 0, d3: 0 }; - let result = try_convert_field::< - crate::generated::blockchain::BlockHeader, - [Felt; 4], - Digest, - >(Some(bad_digest), "account_root"); + let result: Result<[Felt; 4], _> = + Some(bad_digest) + .try_convert_field::("account_root"); let err = result.unwrap_err(); assert!( err.to_string().starts_with("account_root: "), diff --git a/crates/store/src/server/block_producer.rs b/crates/store/src/server/block_producer.rs index f8a5527399..5adcc3a7cf 100644 --- a/crates/store/src/server/block_producer.rs +++ b/crates/store/src/server/block_producer.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; use futures::TryFutureExt; use miden_crypto::dsa::ecdsa_k256_keccak::Signature; -use miden_node_proto::errors::{ConversionError, try_convert_field}; +use miden_node_proto::errors::{ConversionError, TryConvertFieldExt}; use miden_node_proto::generated::store::block_producer_server; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -60,15 +60,14 @@ impl block_producer_server::BlockProducer for StoreApi { .ok_or(ConversionError::missing_field::("block"))?; // Read block header. let header: BlockHeader = - try_convert_field::(block.header, "header")?; + block.header.try_convert_field::("header")?; // Read block body. let body: BlockBody = - try_convert_field::(block.body, "body")?; + block.body.try_convert_field::("body")?; // Read signature. - let signature: Signature = try_convert_field::( - block.signature, - "signature", - )?; + let signature: Signature = block + .signature + .try_convert_field::("signature")?; // Get block inputs from ordered batches. let block_inputs = From e1abb331020ada46514df86406ee275b8405eae3 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 14:27:25 +1300 Subject: [PATCH 18/26] fix commitment field --- crates/proto/src/domain/account.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index dd0e839111..77747b6ee8 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -128,9 +128,7 @@ impl TryFrom for AccountStorageHeader { let slot_type = storage_slot_type_from_raw(slot.slot_type)?; let commitment = slot .commitment - .ok_or(ConversionError::message("value is not in the range 0..MODULUS"))? - .try_into() - .context("commitment")?; + .try_convert_field::("commitment")?; Ok(StorageSlotHeader::new(slot_name, slot_type, commitment)) }) .collect::, ConversionError>>() From 74faae4db9a4dac03a3f881782ae1a19b80302cf Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 14:29:37 +1300 Subject: [PATCH 19/26] fix parsed asset convert --- crates/proto/src/domain/account.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 77747b6ee8..e5159e1632 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -380,11 +380,9 @@ impl TryFrom for AccountVaultDetails { } else { let parsed_assets = Result::, ConversionError>::from_iter(assets.into_iter().map(|asset| { - let asset = asset.asset.ok_or(ConversionError::missing_field::< - proto::primitives::Asset, - >("asset"))?; - let asset = Word::try_from(asset).context("asset")?; - Asset::try_from(asset).map_err(ConversionError::from) + let word: Word = + asset.asset.try_convert_field::("asset")?; + Asset::try_from(word).map_err(ConversionError::from) })) .context("assets")?; Ok(Self::Assets(parsed_assets)) From 526897e4dddcb2a39178f4ac702a30533e4b3300 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 14:33:43 +1300 Subject: [PATCH 20/26] Fix more converts --- crates/proto/src/domain/account.rs | 44 ++++++++---------------------- crates/store/src/server/api.rs | 7 ++--- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index e5159e1632..d77594a8a3 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -554,17 +554,11 @@ impl TryFrom let entries = entries .into_iter() .map(|entry| { - let key = entry - .key - .ok_or(ConversionError::missing_field::("key"))? - .try_into() - .map(StorageMapKey::new) - .context("key")?; - let value = entry - .value - .ok_or(ConversionError::missing_field::("value"))? - .try_into() - .context("value")?; + let key = StorageMapKey::new( + entry.key.try_convert_field::("key")?, + ); + let value = + entry.value.try_convert_field::("value")?; Ok((key, value)) }) .collect::, ConversionError>>() @@ -575,11 +569,7 @@ impl TryFrom let proofs = entries .into_iter() .map(|entry| { - let smt_opening = - entry.proof.ok_or(ConversionError::missing_field::< - StorageMapEntryWithProof, - >("proof"))?; - SmtProof::try_from(smt_opening).context("proof") + entry.proof.try_convert_field::("proof") }) .collect::, ConversionError>>() .context("entries")?; @@ -895,13 +885,12 @@ impl TryFrom for AccountWitnessRecord { let commitment = account_witness_record .commitment .try_convert_field::("commitment")?; + let account_id = account_witness_record + .account_id + .try_convert_field::("account_id")?; let path: SparseMerklePath = account_witness_record .path - .as_ref() - .ok_or(ConversionError::missing_field::("path"))? - .clone() - .try_into() - .context("path")?; + .try_convert_field::("path")?; let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -910,12 +899,7 @@ impl TryFrom for AccountWitnessRecord { ) })?; - Ok(Self { - account_id: account_witness_record - .account_id - .try_convert_field::("account_id")?, - witness, - }) + Ok(Self { account_id, witness }) } } @@ -998,11 +982,7 @@ impl TryFrom for Asset { type Error = ConversionError; fn try_from(value: proto::primitives::Asset) -> Result { - let inner = value - .asset - .ok_or(ConversionError::missing_field::("asset"))?; - let word = Word::try_from(inner).context("asset")?; - + let word: Word = value.asset.try_convert_field::("asset")?; Asset::try_from(word).map_err(ConversionError::from) } } diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 62f1edb148..ff2cab9eff 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use std::sync::Arc; -use miden_node_proto::errors::{ConversionError, ConversionResultExt}; +use miden_node_proto::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; use miden_node_proto::generated as proto; use miden_node_utils::ErrorReport; use miden_protocol::Word; @@ -164,10 +164,7 @@ pub fn read_account_id( where E: From, { - id.ok_or_else(|| ConversionError::missing_field::("account_id"))? - .try_into() - .context("account_id") - .map_err(|e: ConversionError| e.into()) + id.try_convert_field::("account_id").map_err(|e: ConversionError| e.into()) } #[instrument( From 2c77873f7c401655156571dc08420d750fd4713c Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Mar 2026 14:43:19 +1300 Subject: [PATCH 21/26] revert fmt --- crates/rpc/src/server/api.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index 1f887175d6..a0ec88859a 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -534,13 +534,11 @@ fn endpoint_limits(params: &[(&str, usize)]) -> proto::rpc::EndpointLimits { /// Cached RPC query parameter limits. static RPC_LIMITS: LazyLock = LazyLock::new(|| { - use { - QueryParamAccountIdLimit as AccountId, - QueryParamNoteIdLimit as NoteId, - QueryParamNoteTagLimit as NoteTag, - QueryParamNullifierLimit as Nullifier, - QueryParamStorageMapKeyTotalLimit as StorageMapKeyTotal, - }; + use QueryParamAccountIdLimit as AccountId; + use QueryParamNoteIdLimit as NoteId; + use QueryParamNoteTagLimit as NoteTag; + use QueryParamNullifierLimit as Nullifier; + use QueryParamStorageMapKeyTotalLimit as StorageMapKeyTotal; proto::rpc::RpcLimits { endpoints: std::collections::HashMap::from([ From b45de9ab21c89a241519ce224bdc1fb0f5442fb0 Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 18 Mar 2026 12:23:04 +1300 Subject: [PATCH 22/26] DecodeBytesExt --- crates/proto/src/domain/batch.rs | 10 ++++++---- crates/proto/src/domain/block.rs | 17 +++++++--------- crates/proto/src/domain/mempool.rs | 9 ++++----- crates/proto/src/domain/note.rs | 16 ++++++--------- crates/proto/src/domain/transaction.rs | 7 +++---- crates/proto/src/errors/mod.rs | 27 ++++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index 8b6ee62a3d..4490744aa5 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -3,9 +3,9 @@ use std::collections::BTreeMap; use miden_protocol::block::BlockHeader; use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; -use miden_protocol::utils::{Deserializable, Serializable}; +use miden_protocol::utils::Serializable; -use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; use crate::generated as proto; /// Data required for a transaction batch. @@ -42,8 +42,10 @@ impl TryFrom for BatchInputs { .map(<(NoteId, NoteInclusionProof)>::try_from) .collect::>() .context("note_proofs")?, - partial_block_chain: PartialBlockchain::read_from_bytes(&response.partial_block_chain) - .map_err(|source| ConversionError::deserialization("PartialBlockchain", source))?, + partial_block_chain: PartialBlockchain::decode_bytes( + &response.partial_block_chain, + "PartialBlockchain", + )?, }; Ok(result) diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 943b399168..013ce5609c 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -14,10 +14,10 @@ use miden_protocol::block::{ use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; -use miden_protocol::utils::{Deserializable, Serializable}; +use miden_protocol::utils::Serializable; use thiserror::Error; -use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; // BLOCK NUMBER @@ -146,8 +146,7 @@ impl TryFrom<&proto::blockchain::BlockBody> for BlockBody { impl TryFrom for BlockBody { type Error = ConversionError; fn try_from(value: proto::blockchain::BlockBody) -> Result { - BlockBody::read_from_bytes(&value.block_body) - .map_err(|source| ConversionError::deserialization("BlockBody", source)) + BlockBody::decode_bytes(&value.block_body, "BlockBody") } } @@ -262,8 +261,8 @@ impl TryFrom for BlockInputs { .collect::>() .context("unauthenticated_note_proofs")?; - let partial_block_chain = PartialBlockchain::read_from_bytes(&response.partial_block_chain) - .map_err(|source| ConversionError::deserialization("PartialBlockchain", source))?; + let partial_block_chain = + PartialBlockchain::decode_bytes(&response.partial_block_chain, "PartialBlockchain")?; Ok(BlockInputs::new( latest_block_header, @@ -281,8 +280,7 @@ impl TryFrom for BlockInputs { impl TryFrom for PublicKey { type Error = ConversionError; fn try_from(public_key: proto::blockchain::ValidatorPublicKey) -> Result { - PublicKey::read_from_bytes(&public_key.validator_key) - .map_err(|source| ConversionError::deserialization("PublicKey", source)) + PublicKey::decode_bytes(&public_key.validator_key, "PublicKey") } } @@ -304,8 +302,7 @@ impl From<&PublicKey> for proto::blockchain::ValidatorPublicKey { impl TryFrom for Signature { type Error = ConversionError; fn try_from(signature: proto::blockchain::BlockSignature) -> Result { - Signature::read_from_bytes(&signature.signature) - .map_err(|source| ConversionError::deserialization("Signature", source)) + Signature::decode_bytes(&signature.signature, "Signature") } } diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index b3181c3002..9b35e5bf15 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -4,10 +4,10 @@ use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::block::BlockHeader; use miden_protocol::note::Nullifier; use miden_protocol::transaction::TransactionId; -use miden_protocol::utils::{Deserializable, Serializable}; +use miden_protocol::utils::Serializable; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; use crate::generated as proto; #[derive(Debug, Clone)] @@ -108,9 +108,8 @@ impl TryFrom for MempoolEvent { let account_delta = tx .network_account_delta .as_deref() - .map(AccountUpdateDetails::read_from_bytes) - .transpose() - .map_err(|err| ConversionError::deserialization("account_delta", err))?; + .map(|bytes| AccountUpdateDetails::decode_bytes(bytes, "account_delta")) + .transpose()?; Ok(Self::TransactionAdded { id, diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 0ad2b4a383..b62637a930 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -13,11 +13,11 @@ use miden_protocol::note::{ NoteTag, NoteType, }; -use miden_protocol::utils::{Deserializable, Serializable}; +use miden_protocol::utils::Serializable; use miden_protocol::{MastForest, MastNodeId, Word}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; use crate::generated as proto; // NOTE TYPE @@ -64,8 +64,7 @@ impl TryFrom for NoteMetadata { let attachment = if value.attachment.is_empty() { NoteAttachment::default() } else { - NoteAttachment::read_from_bytes(&value.attachment) - .map_err(|err| ConversionError::deserialization("NoteAttachment", err))? + NoteAttachment::decode_bytes(&value.attachment, "NoteAttachment")? }; Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment)) @@ -104,8 +103,7 @@ impl TryFrom for AccountTargetNetworkNote { type Error = ConversionError; fn try_from(value: proto::note::NetworkNote) -> Result { - let details = NoteDetails::read_from_bytes(&value.details) - .map_err(|err| ConversionError::deserialization("NoteDetails", err))?; + let details = NoteDetails::decode_bytes(&value.details, "NoteDetails")?; let (assets, recipient) = details.into_parts(); let metadata: NoteMetadata = value.metadata.try_convert_field::("metadata")?; @@ -212,8 +210,7 @@ impl TryFrom for Note { .details .ok_or(ConversionError::missing_field::("details"))?; - let note_details = NoteDetails::read_from_bytes(&details) - .map_err(|err| ConversionError::deserialization("NoteDetails", err))?; + let note_details = NoteDetails::decode_bytes(&details, "NoteDetails")?; let (assets, recipient) = note_details.into_parts(); Ok(Note::new(assets, metadata, recipient)) @@ -263,8 +260,7 @@ impl TryFrom for NoteScript { fn try_from(value: proto::note::NoteScript) -> Result { let proto::note::NoteScript { entrypoint, mast } = value; - let mast = MastForest::read_from_bytes(&mast) - .map_err(|err| ConversionError::deserialization("note_script.mast", err))?; + let mast = MastForest::decode_bytes(&mast, "note_script.mast")?; let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast) .map_err(|err| ConversionError::deserialization("note_script.entrypoint", err))?; diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index b7fe668f50..3c45def208 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -1,9 +1,9 @@ use miden_protocol::Word; use miden_protocol::note::Nullifier; use miden_protocol::transaction::{InputNoteCommitment, TransactionId}; -use miden_protocol::utils::{Deserializable, Serializable}; +use miden_protocol::utils::Serializable; -use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; use crate::generated as proto; // FROM TRANSACTION ID @@ -82,7 +82,6 @@ impl TryFrom for InputNoteCommitment { let mut bytes = Vec::new(); nullifier.write_into(&mut bytes); header.write_into(&mut bytes); - InputNoteCommitment::read_from_bytes(&bytes) - .map_err(|err| ConversionError::deserialization("InputNoteCommitment", err)) + InputNoteCommitment::decode_bytes(&bytes, "InputNoteCommitment") } } diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 556c99d377..5f4bb993e1 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -214,6 +214,33 @@ where } } +// BYTE DESERIALIZATION EXTENSION TRAIT +// ================================================================================================ + +/// Extension trait on [`Deserializable`](miden_protocol::utils::Deserializable) types to +/// deserialize from bytes and wrap errors as [`ConversionError`]. +/// +/// This removes the boilerplate of calling `T::read_from_bytes(&bytes)` followed by +/// `.map_err(|source| ConversionError::deserialization("T", source))`: +/// +/// ```rust,ignore +/// // Before: +/// BlockBody::read_from_bytes(&value.block_body) +/// .map_err(|source| ConversionError::deserialization("BlockBody", source)) +/// +/// // After: +/// BlockBody::decode_bytes(&value.block_body, "BlockBody") +/// ``` +pub trait DecodeBytesExt: miden_protocol::utils::Deserializable { + /// Deserialize from bytes, wrapping any error as a [`ConversionError`]. + fn decode_bytes(bytes: &[u8], entity: &'static str) -> Result { + Self::read_from_bytes(bytes) + .map_err(|source| ConversionError::deserialization(entity, source)) + } +} + +impl DecodeBytesExt for T {} + // FROM IMPLS FOR EXTERNAL ERROR TYPES // ================================================================================================ From 1e58ecd768bec1655fbcb239b7d8e8948cdf2cf5 Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 18 Mar 2026 12:51:15 +1300 Subject: [PATCH 23/26] GrpcDecodeExt --- crates/block-producer/src/store/mod.rs | 12 +-- crates/proto/src/domain/account.rs | 111 +++++++++------------- crates/proto/src/domain/batch.rs | 10 +- crates/proto/src/domain/block.rs | 55 ++++------- crates/proto/src/domain/mempool.rs | 16 +--- crates/proto/src/domain/merkle.rs | 15 ++- crates/proto/src/domain/note.rs | 20 ++-- crates/proto/src/domain/nullifier.rs | 13 +-- crates/proto/src/domain/transaction.rs | 10 +- crates/proto/src/errors/mod.rs | 79 +++++++++------ crates/store/src/server/api.rs | 6 +- crates/store/src/server/block_producer.rs | 17 ++-- 12 files changed, 160 insertions(+), 204 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index 3431b40703..d2a2a750ab 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -5,7 +5,7 @@ use std::num::NonZeroU32; use itertools::Itertools; use miden_node_proto::clients::{Builder, StoreBlockProducerClient}; use miden_node_proto::domain::batch::BatchInputs; -use miden_node_proto::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use miden_node_proto::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt}; use miden_node_proto::{AccountState, generated as proto}; use miden_node_utils::formatting::format_opt; use miden_protocol::Word; @@ -70,16 +70,14 @@ impl TryFrom for TransactionInputs { type Error = ConversionError; fn try_from(response: proto::store::TransactionInputs) -> Result { + let decoder = response.decoder(); let AccountState { account_id, account_commitment } = - response - .account_state - .try_convert_field::("account_state")?; + decoder.decode_field("account_state", response.account_state)?; let mut nullifiers = HashMap::new(); for nullifier_record in response.nullifiers { - let nullifier = nullifier_record.nullifier.try_convert_field::< - proto::store::transaction_inputs::NullifierTransactionInputRecord, - >("nullifier")?; + let decoder = nullifier_record.decoder(); + let nullifier = decoder.decode_field("nullifier", nullifier_record.nullifier)?; // Note that this intentionally maps 0 to None as this is the definition used in // protobuf. diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index d77594a8a3..bc273e28f8 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -25,7 +25,7 @@ use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError}; use thiserror::Error; use super::try_convert; -use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt}; use crate::generated::{self as proto}; #[cfg(test)] @@ -124,11 +124,10 @@ impl TryFrom for AccountStorageHeader { let slot_headers = slots .into_iter() .map(|slot| { + let decoder = slot.decoder(); let slot_name = StorageSlotName::new(slot.slot_name)?; let slot_type = storage_slot_type_from_raw(slot.slot_type)?; - let commitment = slot - .commitment - .try_convert_field::("commitment")?; + let commitment = decoder.decode_field("commitment", slot.commitment)?; Ok(StorageSlotHeader::new(slot_name, slot_type, commitment)) }) .collect::, ConversionError>>() @@ -153,10 +152,10 @@ impl TryFrom for AccountRequest { type Error = ConversionError; fn try_from(value: proto::rpc::AccountRequest) -> Result { + let decoder = value.decoder(); let proto::rpc::AccountRequest { account_id, block_num, details } = value; - let account_id = - account_id.try_convert_field::("account_id")?; + let account_id = decoder.decode_field("account_id", account_id)?; let block_num = block_num.map(Into::into); let details = details.map(TryFrom::try_from).transpose().context("details")?; @@ -215,15 +214,14 @@ impl TryFrom Result { + let decoder = value.decoder(); let proto::rpc::account_request::account_detail_request::StorageMapDetailRequest { slot_name, slot_data, } = value; let slot_name = StorageSlotName::new(slot_name).context("slot_name")?; - let slot_data = slot_data.try_convert_field::< - proto::rpc::account_request::account_detail_request::StorageMapDetailRequest, - >("slot_data")?; + let slot_data = decoder.decode_field("slot_data", slot_data)?; Ok(StorageMapRequest { slot_name, slot_data }) } @@ -268,6 +266,7 @@ impl TryFrom for AccountHeader { type Error = ConversionError; fn try_from(value: proto::account::AccountHeader) -> Result { + let decoder = value.decoder(); let proto::account::AccountHeader { account_id, vault_root, @@ -276,14 +275,10 @@ impl TryFrom for AccountHeader { nonce, } = value; - let account_id = - account_id.try_convert_field::("account_id")?; - let vault_root = - vault_root.try_convert_field::("vault_root")?; - let storage_commitment = storage_commitment - .try_convert_field::("storage_commitment")?; - let code_commitment = code_commitment - .try_convert_field::("code_commitment")?; + let account_id = decoder.decode_field("account_id", account_id)?; + let vault_root = decoder.decode_field("vault_root", vault_root)?; + let storage_commitment = decoder.decode_field("storage_commitment", storage_commitment)?; + let code_commitment = decoder.decode_field("code_commitment", code_commitment)?; let nonce = nonce.try_into().map_err(ConversionError::message).context("nonce")?; Ok(AccountHeader::new( @@ -380,8 +375,8 @@ impl TryFrom for AccountVaultDetails { } else { let parsed_assets = Result::, ConversionError>::from_iter(assets.into_iter().map(|asset| { - let word: Word = - asset.asset.try_convert_field::("asset")?; + let decoder = asset.decoder(); + let word: Word = decoder.decode_field("asset", asset.asset)?; Asset::try_from(word).map_err(ConversionError::from) })) .context("assets")?; @@ -526,8 +521,6 @@ impl TryFrom value: proto::rpc::account_storage_details::AccountStorageMapDetails, ) -> Result { use proto::rpc::account_storage_details::account_storage_map_details::{ - all_map_entries::StorageMapEntry, - map_entries_with_proofs::StorageMapEntryWithProof, AllMapEntries, Entries as ProtoEntries, MapEntriesWithProofs, @@ -554,11 +547,9 @@ impl TryFrom let entries = entries .into_iter() .map(|entry| { - let key = StorageMapKey::new( - entry.key.try_convert_field::("key")?, - ); - let value = - entry.value.try_convert_field::("value")?; + let decoder = entry.decoder(); + let key = StorageMapKey::new(decoder.decode_field("key", entry.key)?); + let value = decoder.decode_field("value", entry.value)?; Ok((key, value)) }) .collect::, ConversionError>>() @@ -569,7 +560,8 @@ impl TryFrom let proofs = entries .into_iter() .map(|entry| { - entry.proof.try_convert_field::("proof") + let decoder = entry.decoder(); + decoder.decode_field("proof", entry.proof) }) .collect::, ConversionError>>() .context("entries")?; @@ -668,9 +660,10 @@ impl TryFrom for AccountStorageDetails { type Error = ConversionError; fn try_from(value: proto::rpc::AccountStorageDetails) -> Result { + let decoder = value.decoder(); let proto::rpc::AccountStorageDetails { header, map_details } = value; - let header = header.try_convert_field::("header")?; + let header = decoder.decode_field("header", header)?; let map_details = try_convert(map_details).collect::, _>>().context("map_details")?; @@ -719,13 +712,14 @@ impl TryFrom for AccountResponse { type Error = ConversionError; fn try_from(value: proto::rpc::AccountResponse) -> Result { + let decoder = value.decoder(); let proto::rpc::AccountResponse { block_num, witness, details } = value; let block_num = block_num .ok_or(ConversionError::missing_field::("block_num"))? .into(); - let witness = witness.try_convert_field::("witness")?; + let witness = decoder.decode_field("witness", witness)?; let details = details.map(TryFrom::try_from).transpose().context("details")?; @@ -778,6 +772,7 @@ impl TryFrom for AccountDetails { type Error = ConversionError; fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result { + let decoder = value.decoder(); let proto::rpc::account_response::AccountDetails { header, code, @@ -785,14 +780,11 @@ impl TryFrom for AccountDetails { storage_details, } = value; - let account_header = - header.try_convert_field::("header")?; + let account_header = decoder.decode_field("header", header)?; - let storage_details = storage_details - .try_convert_field::("storage_details")?; + let storage_details = decoder.decode_field("storage_details", storage_details)?; - let vault_details = vault_details - .try_convert_field::("vault_details")?; + let vault_details = decoder.decode_field("vault_details", vault_details)?; let account_code = code; Ok(AccountDetails { @@ -834,15 +826,10 @@ impl TryFrom for AccountWitness { type Error = ConversionError; fn try_from(account_witness: proto::account::AccountWitness) -> Result { - let witness_id = account_witness - .witness_id - .try_convert_field::("witness_id")?; - let commitment = account_witness - .commitment - .try_convert_field::("commitment")?; - let path = account_witness - .path - .try_convert_field::("path")?; + let decoder = account_witness.decoder(); + let witness_id = decoder.decode_field("witness_id", account_witness.witness_id)?; + let commitment = decoder.decode_field("commitment", account_witness.commitment)?; + let path = decoder.decode_field("path", account_witness.path)?; AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -879,18 +866,11 @@ impl TryFrom for AccountWitnessRecord { fn try_from( account_witness_record: proto::account::AccountWitness, ) -> Result { - let witness_id = account_witness_record - .witness_id - .try_convert_field::("witness_id")?; - let commitment = account_witness_record - .commitment - .try_convert_field::("commitment")?; - let account_id = account_witness_record - .account_id - .try_convert_field::("account_id")?; - let path: SparseMerklePath = account_witness_record - .path - .try_convert_field::("path")?; + let decoder = account_witness_record.decoder(); + let witness_id = decoder.decode_field("witness_id", account_witness_record.witness_id)?; + let commitment = decoder.decode_field("commitment", account_witness_record.commitment)?; + let account_id = decoder.decode_field("account_id", account_witness_record.account_id)?; + let path: SparseMerklePath = decoder.decode_field("path", account_witness_record.path)?; let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -942,17 +922,11 @@ impl TryFrom fo fn try_from( from: proto::store::transaction_inputs::AccountTransactionInputRecord, ) -> Result { - let account_id = from - .account_id - .try_convert_field::( - "account_id", - )?; - - let account_commitment = from - .account_commitment - .try_convert_field::( - "account_commitment", - )?; + let decoder = from.decoder(); + let account_id = decoder.decode_field("account_id", from.account_id)?; + + let account_commitment = + decoder.decode_field("account_commitment", from.account_commitment)?; // If the commitment is equal to `Word::empty()`, it signifies that this is a new // account which is not yet present in the Store. @@ -982,7 +956,8 @@ impl TryFrom for Asset { type Error = ConversionError; fn try_from(value: proto::primitives::Asset) -> Result { - let word: Word = value.asset.try_convert_field::("asset")?; + let decoder = value.decoder(); + let word: Word = decoder.decode_field("asset", value.asset)?; Asset::try_from(word).map_err(ConversionError::from) } } diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index 4490744aa5..699b6d47fd 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -5,7 +5,7 @@ use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::Serializable; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; use crate::generated as proto; /// Data required for a transaction batch. @@ -30,12 +30,10 @@ impl TryFrom for BatchInputs { type Error = ConversionError; fn try_from(response: proto::store::BatchInputs) -> Result { + let decoder = response.decoder(); let result = Self { - batch_reference_block_header: response - .batch_reference_block_header - .try_convert_field::( - "block_header", - )?, + batch_reference_block_header: decoder + .decode_field("block_header", response.batch_reference_block_header)?, note_proofs: response .note_proofs .iter() diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 013ce5609c..e1aa533b18 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -17,7 +17,7 @@ use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::Serializable; use thiserror::Error; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; // BLOCK NUMBER @@ -75,33 +75,18 @@ impl TryFrom for BlockHeader { type Error = ConversionError; fn try_from(value: proto::blockchain::BlockHeader) -> Result { - let prev_block_commitment = value - .prev_block_commitment - .try_convert_field::("prev_block_commitment")?; - let chain_commitment = value - .chain_commitment - .try_convert_field::("chain_commitment")?; - let account_root = value - .account_root - .try_convert_field::("account_root")?; - let nullifier_root = value - .nullifier_root - .try_convert_field::("nullifier_root")?; - let note_root = value - .note_root - .try_convert_field::("note_root")?; - let tx_commitment = value - .tx_commitment - .try_convert_field::("tx_commitment")?; - let tx_kernel_commitment = value - .tx_kernel_commitment - .try_convert_field::("tx_kernel_commitment")?; - let validator_key = value - .validator_key - .try_convert_field::("validator_key")?; - let fee_parameters = value - .fee_parameters - .try_convert_field::("fee_parameters")?; + let decoder = value.decoder(); + let prev_block_commitment = + decoder.decode_field("prev_block_commitment", value.prev_block_commitment)?; + let chain_commitment = decoder.decode_field("chain_commitment", value.chain_commitment)?; + let account_root = decoder.decode_field("account_root", value.account_root)?; + let nullifier_root = decoder.decode_field("nullifier_root", value.nullifier_root)?; + let note_root = decoder.decode_field("note_root", value.note_root)?; + let tx_commitment = decoder.decode_field("tx_commitment", value.tx_commitment)?; + let tx_kernel_commitment = + decoder.decode_field("tx_kernel_commitment", value.tx_kernel_commitment)?; + let validator_key = decoder.decode_field("validator_key", value.validator_key)?; + let fee_parameters = decoder.decode_field("fee_parameters", value.fee_parameters)?; Ok(BlockHeader::new( value.version, @@ -180,11 +165,10 @@ impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { impl TryFrom for SignedBlock { type Error = ConversionError; fn try_from(value: proto::blockchain::SignedBlock) -> Result { - let header = value.header.try_convert_field::("header")?; - let body = value.body.try_convert_field::("body")?; - let signature = value - .signature - .try_convert_field::("signature")?; + let decoder = value.decoder(); + let header = decoder.decode_field("header", value.header)?; + let body = decoder.decode_field("body", value.body)?; + let signature = decoder.decode_field("signature", value.signature)?; Ok(SignedBlock::new_unchecked(header, body, signature)) } @@ -229,10 +213,9 @@ impl TryFrom for BlockInputs { type Error = ConversionError; fn try_from(response: proto::store::BlockInputs) -> Result { + let decoder = response.decoder(); let latest_block_header: BlockHeader = - response - .latest_block_header - .try_convert_field::("latest_block_header")?; + decoder.decode_field("latest_block_header", response.latest_block_header)?; let account_witnesses = response .account_witnesses diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index 9b35e5bf15..e5cde04f35 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -7,7 +7,7 @@ use miden_protocol::transaction::TransactionId; use miden_protocol::utils::Serializable; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; use crate::generated as proto; #[derive(Debug, Clone)] @@ -88,11 +88,8 @@ impl TryFrom for MempoolEvent { match event { proto::block_producer::mempool_event::Event::TransactionAdded(tx) => { - let id = tx - .id - .try_convert_field::( - "id", - )?; + let decoder = tx.decoder(); + let id = decoder.decode_field("id", tx.id)?; let nullifiers = tx .nullifiers .into_iter() @@ -119,11 +116,8 @@ impl TryFrom for MempoolEvent { }) }, proto::block_producer::mempool_event::Event::BlockCommitted(block_committed) => { - let header = block_committed - .block_header - .try_convert_field::( - "block_header", - )?; + let decoder = block_committed.decoder(); + let header = decoder.decode_field("block_header", block_committed.block_header)?; let header = Box::new(header); let txs = block_committed .transactions diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index cd735dc74b..3ebc85d95d 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -4,7 +4,7 @@ use miden_protocol::crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath}; use crate::domain::{convert, try_convert}; -use crate::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt}; use crate::generated as proto; // MERKLE PATH @@ -155,9 +155,9 @@ impl TryFrom for (Word, Word) { type Error = ConversionError; fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { - let key: Word = entry.key.try_convert_field::("key")?; - let value: Word = - entry.value.try_convert_field::("value")?; + let decoder = entry.decoder(); + let key: Word = decoder.decode_field("key", entry.key)?; + let value: Word = decoder.decode_field("value", entry.value)?; Ok((key, value)) } @@ -179,10 +179,9 @@ impl TryFrom for SmtProof { type Error = ConversionError; fn try_from(opening: proto::primitives::SmtOpening) -> Result { - let path: SparseMerklePath = - opening.path.try_convert_field::("path")?; - let leaf: SmtLeaf = - opening.leaf.try_convert_field::("leaf")?; + let decoder = opening.decoder(); + let path: SparseMerklePath = decoder.decode_field("path", opening.path)?; + let leaf: SmtLeaf = decoder.decode_field("leaf", opening.leaf)?; Ok(SmtProof::new(path, leaf)?) } diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index b62637a930..55e93b5387 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -17,7 +17,7 @@ use miden_protocol::utils::Serializable; use miden_protocol::{MastForest, MastNodeId, Word}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; use crate::generated as proto; // NOTE TYPE @@ -53,7 +53,8 @@ impl TryFrom for NoteMetadata { type Error = ConversionError; fn try_from(value: proto::note::NoteMetadata) -> Result { - let sender = value.sender.try_convert_field::("sender")?; + let decoder = value.decoder(); + let sender = decoder.decode_field("sender", value.sender)?; let note_type = proto::note::NoteType::try_from(value.note_type) .map_err(|_| ConversionError::message("enum variant discriminant out of range"))? .try_into() @@ -103,10 +104,10 @@ impl TryFrom for AccountTargetNetworkNote { type Error = ConversionError; fn try_from(value: proto::note::NetworkNote) -> Result { + let decoder = value.decoder(); let details = NoteDetails::decode_bytes(&value.details, "NoteDetails")?; let (assets, recipient) = details.into_parts(); - let metadata: NoteMetadata = - value.metadata.try_convert_field::("metadata")?; + let metadata: NoteMetadata = decoder.decode_field("metadata", value.metadata)?; let note = Note::new(assets, metadata, recipient); AccountTargetNetworkNote::new(note).map_err(ConversionError::from) } @@ -203,8 +204,8 @@ impl TryFrom for Note { type Error = ConversionError; fn try_from(proto_note: proto::note::Note) -> Result { - let metadata: NoteMetadata = - proto_note.metadata.try_convert_field::("metadata")?; + let decoder = proto_note.decoder(); + let metadata: NoteMetadata = decoder.decode_field("metadata", proto_note.metadata)?; let details = proto_note .details @@ -233,10 +234,9 @@ impl TryFrom for NoteHeader { type Error = ConversionError; fn try_from(value: proto::note::NoteHeader) -> Result { - let note_id_word: Word = - value.note_id.try_convert_field::("note_id")?; - let metadata: NoteMetadata = - value.metadata.try_convert_field::("metadata")?; + let decoder = value.decoder(); + let note_id_word: Word = decoder.decode_field("note_id", value.note_id)?; + let metadata: NoteMetadata = decoder.decode_field("metadata", value.metadata)?; Ok(NoteHeader::new(NoteId::from_raw(note_id_word), metadata)) } diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index b1669126f9..1a6712bdfc 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -2,7 +2,7 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::smt::SmtProof; use miden_protocol::note::Nullifier; -use crate::errors::{ConversionError, TryConvertFieldExt}; +use crate::errors::{ConversionError, GrpcDecodeExt}; use crate::generated as proto; // FROM NULLIFIER @@ -47,15 +47,10 @@ impl TryFrom for NullifierWitnessR fn try_from( nullifier_witness_record: proto::store::block_inputs::NullifierWitness, ) -> Result { + let decoder = nullifier_witness_record.decoder(); Ok(Self { - nullifier: nullifier_witness_record - .nullifier - .try_convert_field::( - "nullifier", - )?, - proof: nullifier_witness_record - .opening - .try_convert_field::("opening")?, + nullifier: decoder.decode_field("nullifier", nullifier_witness_record.nullifier)?, + proof: decoder.decode_field("opening", nullifier_witness_record.opening)?, }) } } diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index 3c45def208..79cbe23be8 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -3,7 +3,7 @@ use miden_protocol::note::Nullifier; use miden_protocol::transaction::{InputNoteCommitment, TransactionId}; use miden_protocol::utils::Serializable; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, TryConvertFieldExt}; +use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; use crate::generated as proto; // FROM TRANSACTION ID @@ -49,7 +49,8 @@ impl TryFrom for TransactionId { type Error = ConversionError; fn try_from(value: proto::transaction::TransactionId) -> Result { - value.id.try_convert_field::("id") + let decoder = value.decoder(); + decoder.decode_field("id", value.id) } } @@ -69,9 +70,8 @@ impl TryFrom for InputNoteCommitment { type Error = ConversionError; fn try_from(value: proto::transaction::InputNoteCommitment) -> Result { - let nullifier: Nullifier = value - .nullifier - .try_convert_field::("nullifier")?; + let decoder = value.decoder(); + let nullifier: Nullifier = decoder.decode_field("nullifier", value.nullifier)?; let header: Option = value.header.map(TryInto::try_into).transpose().context("header")?; diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 5f4bb993e1..341d4ed6a5 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -1,5 +1,6 @@ use std::any::type_name; use std::fmt; +use std::marker::PhantomData; // Re-export the GrpcError derive macro for convenience pub use miden_node_grpc_error_macro::GrpcError; @@ -173,47 +174,65 @@ impl> ConversionResultExt for Result { } } -// FIELD CONVERSION HELPERS +// GRPC STRUCT DECODER // ================================================================================================ -/// Extension trait on `Option` to convert a required protobuf field in one step. +/// Zero-cost struct decoder that captures the parent proto message type. /// -/// Combines the missing-field check, fallible conversion, and context injection: +/// Created via [`GrpcDecodeExt::decoder`] which infers the parent type from the value: /// /// ```rust,ignore /// // Before: -/// let body = block.body -/// .ok_or(ConversionError::missing_field::("body"))? -/// .try_into() -/// .context("body")?; +/// let body = block.body.try_convert_field::("body")?; +/// let header = block.header.try_convert_field::("header")?; /// /// // After: -/// let body = block.body.try_convert_field::("body")?; +/// let decoder = block.decoder(); +/// let body = decoder.decode_field("body", block.body)?; +/// let header = decoder.decode_field("header", block.header)?; /// ``` -pub trait TryConvertFieldExt { - /// Unwraps the `Option`, returning a [`ConversionError::missing_field`] for `None`, - /// then converts via `TryInto` with field context on failure. +pub struct GrpcStructDecoder(PhantomData); + +impl Default for GrpcStructDecoder { + /// Create a decoder for the given parent message type directly. /// - /// `M` is the parent protobuf message that contains this field. - fn try_convert_field(self, name: &'static str) - -> Result; + /// Prefer [`GrpcDecodeExt::decoder`] when a value of type `M` is available, as it infers + /// the type automatically. + fn default() -> Self { + Self(PhantomData) + } } -impl TryConvertFieldExt for Option -where - T: TryInto, - T::Error: Into, -{ - fn try_convert_field( - self, +impl GrpcStructDecoder { + /// Decode a required optional field: checks for `None`, converts via `TryInto`, and adds + /// field context on error. + pub fn decode_field( + &self, name: &'static str, - ) -> Result { - self.ok_or_else(|| ConversionError::missing_field::(name))? + value: Option, + ) -> Result + where + T: TryInto, + T::Error: Into, + { + value + .ok_or_else(|| ConversionError::missing_field::(name))? .try_into() .context(name) } } +/// Extension trait on [`prost::Message`] types to create a [`GrpcStructDecoder`] with the parent +/// type inferred from the value. +pub trait GrpcDecodeExt: prost::Message + Sized { + /// Create a decoder that uses `Self` as the parent message type for error reporting. + fn decoder(&self) -> GrpcStructDecoder { + GrpcStructDecoder(PhantomData) + } +} + +impl GrpcDecodeExt for T {} + // BYTE DESERIALIZATION EXTENSION TRAIT // ================================================================================================ @@ -320,15 +339,14 @@ mod tests { } #[test] - fn test_try_convert_field_missing() { + fn test_decode_field_missing() { use miden_protocol::Felt; use crate::generated::primitives::Digest; + let decoder = GrpcStructDecoder::::default(); let field: Option = None; - let result = - field.try_convert_field::("account_root"); - let result: Result<[Felt; 4], _> = result; + let result: Result<[Felt; 4], _> = decoder.decode_field("account_root", field); let err = result.unwrap_err(); assert!( err.to_string().contains("account_root") && err.to_string().contains("missing"), @@ -337,16 +355,15 @@ mod tests { } #[test] - fn test_try_convert_field_conversion_error() { + fn test_decode_field_conversion_error() { use miden_protocol::Felt; use crate::generated::primitives::Digest; + let decoder = GrpcStructDecoder::::default(); // Create a digest with an out-of-range value. let bad_digest = Digest { d0: u64::MAX, d1: 0, d2: 0, d3: 0 }; - let result: Result<[Felt; 4], _> = - Some(bad_digest) - .try_convert_field::("account_root"); + let result: Result<[Felt; 4], _> = decoder.decode_field("account_root", Some(bad_digest)); let err = result.unwrap_err(); assert!( err.to_string().starts_with("account_root: "), diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index ff2cab9eff..10b7854fa9 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use std::sync::Arc; -use miden_node_proto::errors::{ConversionError, ConversionResultExt, TryConvertFieldExt}; +use miden_node_proto::errors::{ConversionError, ConversionResultExt, GrpcStructDecoder}; use miden_node_proto::generated as proto; use miden_node_utils::ErrorReport; use miden_protocol::Word; @@ -164,7 +164,9 @@ pub fn read_account_id( where E: From, { - id.try_convert_field::("account_id").map_err(|e: ConversionError| e.into()) + GrpcStructDecoder::::default() + .decode_field("account_id", id) + .map_err(|e: ConversionError| e.into()) } #[instrument( diff --git a/crates/store/src/server/block_producer.rs b/crates/store/src/server/block_producer.rs index 5adcc3a7cf..4fd676227e 100644 --- a/crates/store/src/server/block_producer.rs +++ b/crates/store/src/server/block_producer.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; use futures::TryFutureExt; use miden_crypto::dsa::ecdsa_k256_keccak::Signature; -use miden_node_proto::errors::{ConversionError, TryConvertFieldExt}; +use miden_node_proto::errors::{ConversionError, GrpcDecodeExt}; use miden_node_proto::generated::store::block_producer_server; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; @@ -58,16 +58,11 @@ impl block_producer_server::BlockProducer for StoreApi { let block = request .block .ok_or(ConversionError::missing_field::("block"))?; - // Read block header. - let header: BlockHeader = - block.header.try_convert_field::("header")?; - // Read block body. - let body: BlockBody = - block.body.try_convert_field::("body")?; - // Read signature. - let signature: Signature = block - .signature - .try_convert_field::("signature")?; + // Decode block fields. + let decoder = block.decoder(); + let header: BlockHeader = decoder.decode_field("header", block.header)?; + let body: BlockBody = decoder.decode_field("body", block.body)?; + let signature: Signature = decoder.decode_field("signature", block.signature)?; // Get block inputs from ordered batches. let block_inputs = From 61c593f5bdf304e1364714314a5d1e97cf93232a Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 18 Mar 2026 13:26:13 +1300 Subject: [PATCH 24/26] add proc maro --- crates/block-producer/src/store/mod.rs | 12 +- crates/grpc-error-macro/Cargo.toml | 2 +- crates/grpc-error-macro/src/lib.rs | 161 ++++++++++++++++++++++++- crates/proto/src/domain/account.rs | 117 +++++++----------- crates/proto/src/domain/batch.rs | 13 +- crates/proto/src/domain/block.rs | 53 ++++---- crates/proto/src/domain/merkle.rs | 14 +-- crates/proto/src/domain/note.rs | 26 ++-- crates/proto/src/domain/nullifier.rs | 8 +- crates/proto/src/domain/transaction.rs | 16 ++- crates/proto/src/errors/mod.rs | 2 +- 11 files changed, 284 insertions(+), 140 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index d2a2a750ab..bfdff4b8cc 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -5,7 +5,12 @@ use std::num::NonZeroU32; use itertools::Itertools; use miden_node_proto::clients::{Builder, StoreBlockProducerClient}; use miden_node_proto::domain::batch::BatchInputs; -use miden_node_proto::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt}; +use miden_node_proto::errors::{ + ConversionError, + ConversionResultExt, + GrpcDecodeExt as _, + grpc_decode, +}; use miden_node_proto::{AccountState, generated as proto}; use miden_node_utils::formatting::format_opt; use miden_protocol::Word; @@ -66,13 +71,12 @@ impl Display for TransactionInputs { } } +#[grpc_decode] impl TryFrom for TransactionInputs { type Error = ConversionError; fn try_from(response: proto::store::TransactionInputs) -> Result { - let decoder = response.decoder(); - let AccountState { account_id, account_commitment } = - decoder.decode_field("account_state", response.account_state)?; + let AccountState { account_id, account_commitment } = response.account_state.decode()?; let mut nullifiers = HashMap::new(); for nullifier_record in response.nullifiers { diff --git a/crates/grpc-error-macro/Cargo.toml b/crates/grpc-error-macro/Cargo.toml index 4fd40b0b2d..8431376845 100644 --- a/crates/grpc-error-macro/Cargo.toml +++ b/crates/grpc-error-macro/Cargo.toml @@ -19,4 +19,4 @@ proc-macro = true [dependencies] quote = "1.0" -syn = { features = ["full"], version = "2.0" } +syn = { features = ["full", "visit-mut"], version = "2.0" } diff --git a/crates/grpc-error-macro/src/lib.rs b/crates/grpc-error-macro/src/lib.rs index a898d49dc4..2a491f0d05 100644 --- a/crates/grpc-error-macro/src/lib.rs +++ b/crates/grpc-error-macro/src/lib.rs @@ -17,10 +17,10 @@ //! #[error("database error")] //! #[grpc(internal)] //! DatabaseError(#[from] DatabaseError), -//! +//! //! #[error("malformed script root")] //! DeserializationFailed, -//! +//! //! #[error("script with given root doesn't exist")] //! ScriptNotFound, //! } @@ -28,6 +28,7 @@ use proc_macro::TokenStream; use quote::quote; +use syn::visit_mut::VisitMut; use syn::{Data, DeriveInput, Fields, Ident, parse_macro_input}; /// Derives the `GrpcError` trait for an error enum. @@ -178,3 +179,159 @@ pub fn derive_grpc_error(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } + +// GRPC DECODE ATTRIBUTE MACRO +// ================================================================================================ + +/// Attribute macro that rewrites `.decode()` shorthand into +/// `decoder.decode_field("field_name", value.field_name)` calls. +/// +/// Place on an `impl TryFrom for DomainType` block. The macro will: +/// 1. Find all `.field.decode()` calls in each method body +/// 2. Inject `let decoder = .decoder();` at the top of the method +/// 3. Rewrite each `.decode()` call to `decoder.decode_field("field", .field)` +/// +/// The field name string is extracted from the last segment of the field access expression. +/// Calls inside closures are **not** rewritten (closures have their own receiver scope). +/// +/// # Example +/// +/// ```rust,ignore +/// #[grpc_decode] +/// impl TryFrom for BlockHeader { +/// type Error = ConversionError; +/// fn try_from(value: proto::blockchain::BlockHeader) -> Result { +/// let prev = value.prev_block_commitment.decode()?; +/// let chain = value.chain_commitment.decode()?; +/// // Non-decode code passes through unchanged: +/// Ok(BlockHeader::new(value.version, prev, value.block_num.into(), chain)) +/// } +/// } +/// ``` +/// +/// Expands to: +/// +/// ```rust,ignore +/// impl TryFrom for BlockHeader { +/// type Error = ConversionError; +/// fn try_from(value: proto::blockchain::BlockHeader) -> Result { +/// let decoder = value.decoder(); +/// let prev = decoder.decode_field("prev_block_commitment", value.prev_block_commitment)?; +/// let chain = decoder.decode_field("chain_commitment", value.chain_commitment)?; +/// Ok(BlockHeader::new(value.version, prev, value.block_num.into(), chain)) +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn grpc_decode(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut impl_block = parse_macro_input!(item as syn::ItemImpl); + + for item in &mut impl_block.items { + if let syn::ImplItem::Fn(method) = item { + process_method(method); + } + } + + TokenStream::from(quote!(#impl_block)) +} + +/// Processes a single method in the impl block, rewriting `.decode()` calls. +fn process_method(method: &mut syn::ImplItemFn) { + let Some(param_name) = extract_param_name(&method.sig) else { + return; + }; + + let mut rewriter = DecodeRewriter { + param_name: param_name.clone(), + found_decode: false, + closure_depth: 0, + }; + + syn::visit_mut::visit_block_mut(&mut rewriter, &mut method.block); + + if rewriter.found_decode { + let decoder_stmt: syn::Stmt = syn::parse_quote! { + let decoder = #param_name.decoder(); + }; + method.block.stmts.insert(0, decoder_stmt); + } +} + +/// Extracts the first non-self parameter name from a function signature. +fn extract_param_name(sig: &syn::Signature) -> Option { + for arg in &sig.inputs { + if let syn::FnArg::Typed(pat_type) = arg { + if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { + return Some(pat_ident.ident.clone()); + } + } + } + None +} + +/// AST visitor that rewrites `.field.decode()` → +/// `decoder.decode_field("field", .field)`. +struct DecodeRewriter { + /// The function parameter name (e.g., `value`, `response`, `from`). + param_name: Ident, + /// Whether any `.decode()` calls were found and rewritten. + found_decode: bool, + /// Current closure nesting depth — skip rewriting when > 0. + closure_depth: u32, +} + +impl DecodeRewriter { + /// Checks if a field access expression is rooted at the function parameter, + /// and returns the last field name segment if so. + fn extract_field_info(&self, expr: &syn::Expr) -> Option { + if let syn::Expr::Field(field_expr) = expr { + if let syn::Member::Named(field_ident) = &field_expr.member { + if self.root_is_param(expr) { + return Some(field_ident.clone()); + } + } + } + None + } + + /// Recursively checks if the root of a field access chain is the function parameter. + fn root_is_param(&self, expr: &syn::Expr) -> bool { + match expr { + syn::Expr::Path(path) => path.path.is_ident(&self.param_name), + syn::Expr::Field(field) => self.root_is_param(&field.base), + _ => false, + } + } +} + +impl VisitMut for DecodeRewriter { + fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { + // Recurse into children first (bottom-up). + syn::visit_mut::visit_expr_mut(self, expr); + + // Skip rewriting inside closures. + if self.closure_depth > 0 { + return; + } + + // Match: .decode() with no arguments. + if let syn::Expr::MethodCall(mc) = expr { + if mc.method == "decode" && mc.args.is_empty() { + if let Some(field_name) = self.extract_field_info(&mc.receiver) { + let receiver = &mc.receiver; + let name_str = field_name.to_string(); + self.found_decode = true; + *expr = syn::parse_quote! { + decoder.decode_field(#name_str, #receiver) + }; + } + } + } + } + + fn visit_expr_closure_mut(&mut self, closure: &mut syn::ExprClosure) { + self.closure_depth += 1; + syn::visit_mut::visit_expr_closure_mut(self, closure); + self.closure_depth -= 1; + } +} diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index bc273e28f8..163751c401 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -25,7 +25,7 @@ use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError}; use thiserror::Error; use super::try_convert; -use crate::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt}; +use crate::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt as _, grpc_decode}; use crate::generated::{self as proto}; #[cfg(test)] @@ -148,17 +148,14 @@ pub struct AccountRequest { pub details: Option, } +#[grpc_decode] impl TryFrom for AccountRequest { type Error = ConversionError; fn try_from(value: proto::rpc::AccountRequest) -> Result { - let decoder = value.decoder(); - let proto::rpc::AccountRequest { account_id, block_num, details } = value; - - let account_id = decoder.decode_field("account_id", account_id)?; - let block_num = block_num.map(Into::into); - - let details = details.map(TryFrom::try_from).transpose().context("details")?; + let account_id = value.account_id.decode()?; + let block_num = value.block_num.map(Into::into); + let details = value.details.map(TryFrom::try_from).transpose().context("details")?; Ok(AccountRequest { account_id, block_num, details }) } @@ -206,6 +203,7 @@ pub struct StorageMapRequest { pub slot_data: SlotData, } +#[grpc_decode] impl TryFrom for StorageMapRequest { @@ -214,14 +212,8 @@ impl TryFrom Result { - let decoder = value.decoder(); - let proto::rpc::account_request::account_detail_request::StorageMapDetailRequest { - slot_name, - slot_data, - } = value; - - let slot_name = StorageSlotName::new(slot_name).context("slot_name")?; - let slot_data = decoder.decode_field("slot_data", slot_data)?; + let slot_name = StorageSlotName::new(value.slot_name).context("slot_name")?; + let slot_data = value.slot_data.decode()?; Ok(StorageMapRequest { slot_name, slot_data }) } @@ -262,24 +254,16 @@ impl // ACCOUNT HEADER CONVERSIONS //================================================================================================ +#[grpc_decode] impl TryFrom for AccountHeader { type Error = ConversionError; fn try_from(value: proto::account::AccountHeader) -> Result { - let decoder = value.decoder(); - let proto::account::AccountHeader { - account_id, - vault_root, - storage_commitment, - code_commitment, - nonce, - } = value; - - let account_id = decoder.decode_field("account_id", account_id)?; - let vault_root = decoder.decode_field("vault_root", vault_root)?; - let storage_commitment = decoder.decode_field("storage_commitment", storage_commitment)?; - let code_commitment = decoder.decode_field("code_commitment", code_commitment)?; - let nonce = nonce.try_into().map_err(ConversionError::message).context("nonce")?; + let account_id = value.account_id.decode()?; + let vault_root = value.vault_root.decode()?; + let storage_commitment = value.storage_commitment.decode()?; + let code_commitment = value.code_commitment.decode()?; + let nonce = value.nonce.try_into().map_err(ConversionError::message).context("nonce")?; Ok(AccountHeader::new( account_id, @@ -656,17 +640,15 @@ impl AccountStorageDetails { } } +#[grpc_decode] impl TryFrom for AccountStorageDetails { type Error = ConversionError; fn try_from(value: proto::rpc::AccountStorageDetails) -> Result { - let decoder = value.decoder(); - let proto::rpc::AccountStorageDetails { header, map_details } = value; - - let header = decoder.decode_field("header", header)?; - - let map_details = - try_convert(map_details).collect::, _>>().context("map_details")?; + let header = value.header.decode()?; + let map_details = try_convert(value.map_details) + .collect::, _>>() + .context("map_details")?; Ok(Self { header, map_details }) } @@ -708,20 +690,17 @@ pub struct AccountResponse { pub details: Option, } +#[grpc_decode] impl TryFrom for AccountResponse { type Error = ConversionError; fn try_from(value: proto::rpc::AccountResponse) -> Result { - let decoder = value.decoder(); - let proto::rpc::AccountResponse { block_num, witness, details } = value; - - let block_num = block_num + let block_num = value + .block_num .ok_or(ConversionError::missing_field::("block_num"))? .into(); - - let witness = decoder.decode_field("witness", witness)?; - - let details = details.map(TryFrom::try_from).transpose().context("details")?; + let witness = value.witness.decode()?; + let details = value.details.map(TryFrom::try_from).transpose().context("details")?; Ok(AccountResponse { block_num, witness, details }) } @@ -768,24 +747,15 @@ impl AccountDetails { } } +#[grpc_decode] impl TryFrom for AccountDetails { type Error = ConversionError; fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result { - let decoder = value.decoder(); - let proto::rpc::account_response::AccountDetails { - header, - code, - vault_details, - storage_details, - } = value; - - let account_header = decoder.decode_field("header", header)?; - - let storage_details = decoder.decode_field("storage_details", storage_details)?; - - let vault_details = decoder.decode_field("vault_details", vault_details)?; - let account_code = code; + let account_header = value.header.decode()?; + let storage_details = value.storage_details.decode()?; + let vault_details = value.vault_details.decode()?; + let account_code = value.code; Ok(AccountDetails { account_header, @@ -822,14 +792,14 @@ impl From for proto::rpc::account_response::AccountDetails { // ACCOUNT WITNESS // ================================================================================================ +#[grpc_decode] impl TryFrom for AccountWitness { type Error = ConversionError; fn try_from(account_witness: proto::account::AccountWitness) -> Result { - let decoder = account_witness.decoder(); - let witness_id = decoder.decode_field("witness_id", account_witness.witness_id)?; - let commitment = decoder.decode_field("commitment", account_witness.commitment)?; - let path = decoder.decode_field("path", account_witness.path)?; + let witness_id = account_witness.witness_id.decode()?; + let commitment = account_witness.commitment.decode()?; + let path = account_witness.path.decode()?; AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -860,17 +830,17 @@ pub struct AccountWitnessRecord { pub witness: AccountWitness, } +#[grpc_decode] impl TryFrom for AccountWitnessRecord { type Error = ConversionError; fn try_from( account_witness_record: proto::account::AccountWitness, ) -> Result { - let decoder = account_witness_record.decoder(); - let witness_id = decoder.decode_field("witness_id", account_witness_record.witness_id)?; - let commitment = decoder.decode_field("commitment", account_witness_record.commitment)?; - let account_id = decoder.decode_field("account_id", account_witness_record.account_id)?; - let path: SparseMerklePath = decoder.decode_field("path", account_witness_record.path)?; + let witness_id = account_witness_record.witness_id.decode()?; + let commitment = account_witness_record.commitment.decode()?; + let account_id = account_witness_record.account_id.decode()?; + let path: SparseMerklePath = account_witness_record.path.decode()?; let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| { ConversionError::deserialization( @@ -916,17 +886,16 @@ impl Display for AccountState { } } +#[grpc_decode] impl TryFrom for AccountState { type Error = ConversionError; fn try_from( from: proto::store::transaction_inputs::AccountTransactionInputRecord, ) -> Result { - let decoder = from.decoder(); - let account_id = decoder.decode_field("account_id", from.account_id)?; + let account_id = from.account_id.decode()?; - let account_commitment = - decoder.decode_field("account_commitment", from.account_commitment)?; + let account_commitment = from.account_commitment.decode()?; // If the commitment is equal to `Word::empty()`, it signifies that this is a new // account which is not yet present in the Store. @@ -952,12 +921,12 @@ impl From for proto::store::transaction_inputs::AccountTransaction // ASSET // ================================================================================================ +#[grpc_decode] impl TryFrom for Asset { type Error = ConversionError; fn try_from(value: proto::primitives::Asset) -> Result { - let decoder = value.decoder(); - let word: Word = decoder.decode_field("asset", value.asset)?; + let word: Word = value.asset.decode()?; Asset::try_from(word).map_err(ConversionError::from) } } diff --git a/crates/proto/src/domain/batch.rs b/crates/proto/src/domain/batch.rs index 699b6d47fd..cc1aedceed 100644 --- a/crates/proto/src/domain/batch.rs +++ b/crates/proto/src/domain/batch.rs @@ -5,7 +5,13 @@ use miden_protocol::note::{NoteId, NoteInclusionProof}; use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::Serializable; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; +use crate::errors::{ + ConversionError, + ConversionResultExt, + DecodeBytesExt, + GrpcDecodeExt as _, + grpc_decode, +}; use crate::generated as proto; /// Data required for a transaction batch. @@ -26,14 +32,13 @@ impl From for proto::store::BatchInputs { } } +#[grpc_decode] impl TryFrom for BatchInputs { type Error = ConversionError; fn try_from(response: proto::store::BatchInputs) -> Result { - let decoder = response.decoder(); let result = Self { - batch_reference_block_header: decoder - .decode_field("block_header", response.batch_reference_block_header)?, + batch_reference_block_header: response.batch_reference_block_header.decode()?, note_proofs: response .note_proofs .iter() diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index e1aa533b18..7e994613c1 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -1,7 +1,6 @@ use std::collections::BTreeMap; use std::ops::RangeInclusive; -use miden_protocol::account::AccountId; use miden_protocol::block::nullifier_tree::NullifierWitness; use miden_protocol::block::{ BlockBody, @@ -17,7 +16,13 @@ use miden_protocol::transaction::PartialBlockchain; use miden_protocol::utils::Serializable; use thiserror::Error; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; +use crate::errors::{ + ConversionError, + ConversionResultExt, + DecodeBytesExt, + GrpcDecodeExt as _, + grpc_decode, +}; use crate::{AccountWitnessRecord, NullifierWitnessRecord, generated as proto}; // BLOCK NUMBER @@ -71,22 +76,20 @@ impl TryFrom<&proto::blockchain::BlockHeader> for BlockHeader { } } +#[grpc_decode] impl TryFrom for BlockHeader { type Error = ConversionError; fn try_from(value: proto::blockchain::BlockHeader) -> Result { - let decoder = value.decoder(); - let prev_block_commitment = - decoder.decode_field("prev_block_commitment", value.prev_block_commitment)?; - let chain_commitment = decoder.decode_field("chain_commitment", value.chain_commitment)?; - let account_root = decoder.decode_field("account_root", value.account_root)?; - let nullifier_root = decoder.decode_field("nullifier_root", value.nullifier_root)?; - let note_root = decoder.decode_field("note_root", value.note_root)?; - let tx_commitment = decoder.decode_field("tx_commitment", value.tx_commitment)?; - let tx_kernel_commitment = - decoder.decode_field("tx_kernel_commitment", value.tx_kernel_commitment)?; - let validator_key = decoder.decode_field("validator_key", value.validator_key)?; - let fee_parameters = decoder.decode_field("fee_parameters", value.fee_parameters)?; + let prev_block_commitment = value.prev_block_commitment.decode()?; + let chain_commitment = value.chain_commitment.decode()?; + let account_root = value.account_root.decode()?; + let nullifier_root = value.nullifier_root.decode()?; + let note_root = value.note_root.decode()?; + let tx_commitment = value.tx_commitment.decode()?; + let tx_kernel_commitment = value.tx_kernel_commitment.decode()?; + let validator_key = value.validator_key.decode()?; + let fee_parameters = value.fee_parameters.decode()?; Ok(BlockHeader::new( value.version, @@ -162,13 +165,13 @@ impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock { } } +#[grpc_decode] impl TryFrom for SignedBlock { type Error = ConversionError; fn try_from(value: proto::blockchain::SignedBlock) -> Result { - let decoder = value.decoder(); - let header = decoder.decode_field("header", value.header)?; - let body = decoder.decode_field("body", value.body)?; - let signature = decoder.decode_field("signature", value.signature)?; + let header = value.header.decode()?; + let body = value.body.decode()?; + let signature = value.signature.decode()?; Ok(SignedBlock::new_unchecked(header, body, signature)) } @@ -209,13 +212,12 @@ impl From for proto::store::BlockInputs { } } +#[grpc_decode] impl TryFrom for BlockInputs { type Error = ConversionError; fn try_from(response: proto::store::BlockInputs) -> Result { - let decoder = response.decoder(); - let latest_block_header: BlockHeader = - decoder.decode_field("latest_block_header", response.latest_block_header)?; + let latest_block_header: BlockHeader = response.latest_block_header.decode()?; let account_witnesses = response .account_witnesses @@ -304,16 +306,11 @@ impl From<&Signature> for proto::blockchain::BlockSignature { // FEE PARAMETERS // ================================================================================================ +#[grpc_decode] impl TryFrom for FeeParameters { type Error = ConversionError; fn try_from(fee_params: proto::blockchain::FeeParameters) -> Result { - let native_asset_id = fee_params - .native_asset_id - .map(AccountId::try_from) - .ok_or(ConversionError::missing_field::( - "native_asset_id", - ))? - .context("native_asset_id")?; + let native_asset_id = fee_params.native_asset_id.decode()?; let fee_params = FeeParameters::new(native_asset_id, fee_params.verification_base_fee)?; Ok(fee_params) } diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index 3ebc85d95d..287fdb6597 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -4,7 +4,7 @@ use miden_protocol::crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath}; use crate::domain::{convert, try_convert}; -use crate::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt}; +use crate::errors::{ConversionError, ConversionResultExt, GrpcDecodeExt as _, grpc_decode}; use crate::generated as proto; // MERKLE PATH @@ -151,13 +151,13 @@ impl From for proto::primitives::SmtLeaf { // SMT LEAF ENTRY // ------------------------------------------------------------------------------------------------ +#[grpc_decode] impl TryFrom for (Word, Word) { type Error = ConversionError; fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { - let decoder = entry.decoder(); - let key: Word = decoder.decode_field("key", entry.key)?; - let value: Word = decoder.decode_field("value", entry.value)?; + let key: Word = entry.key.decode()?; + let value: Word = entry.value.decode()?; Ok((key, value)) } @@ -175,13 +175,13 @@ impl From<(Word, Word)> for proto::primitives::SmtLeafEntry { // SMT PROOF // ------------------------------------------------------------------------------------------------ +#[grpc_decode] impl TryFrom for SmtProof { type Error = ConversionError; fn try_from(opening: proto::primitives::SmtOpening) -> Result { - let decoder = opening.decoder(); - let path: SparseMerklePath = decoder.decode_field("path", opening.path)?; - let leaf: SmtLeaf = decoder.decode_field("leaf", opening.leaf)?; + let path: SparseMerklePath = opening.path.decode()?; + let leaf: SmtLeaf = opening.leaf.decode()?; Ok(SmtProof::new(path, leaf)?) } diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 55e93b5387..3c7dd3cbe2 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -17,7 +17,13 @@ use miden_protocol::utils::Serializable; use miden_protocol::{MastForest, MastNodeId, Word}; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; +use crate::errors::{ + ConversionError, + ConversionResultExt, + DecodeBytesExt, + GrpcDecodeExt as _, + grpc_decode, +}; use crate::generated as proto; // NOTE TYPE @@ -49,12 +55,12 @@ impl TryFrom for NoteType { // NOTE METADATA // ================================================================================================ +#[grpc_decode] impl TryFrom for NoteMetadata { type Error = ConversionError; fn try_from(value: proto::note::NoteMetadata) -> Result { - let decoder = value.decoder(); - let sender = decoder.decode_field("sender", value.sender)?; + let sender = value.sender.decode()?; let note_type = proto::note::NoteType::try_from(value.note_type) .map_err(|_| ConversionError::message("enum variant discriminant out of range"))? .try_into() @@ -100,14 +106,14 @@ impl From for proto::note::NetworkNote { } } +#[grpc_decode] impl TryFrom for AccountTargetNetworkNote { type Error = ConversionError; fn try_from(value: proto::note::NetworkNote) -> Result { - let decoder = value.decoder(); let details = NoteDetails::decode_bytes(&value.details, "NoteDetails")?; let (assets, recipient) = details.into_parts(); - let metadata: NoteMetadata = decoder.decode_field("metadata", value.metadata)?; + let metadata: NoteMetadata = value.metadata.decode()?; let note = Note::new(assets, metadata, recipient); AccountTargetNetworkNote::new(note).map_err(ConversionError::from) } @@ -200,12 +206,12 @@ impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusion } } +#[grpc_decode] impl TryFrom for Note { type Error = ConversionError; fn try_from(proto_note: proto::note::Note) -> Result { - let decoder = proto_note.decoder(); - let metadata: NoteMetadata = decoder.decode_field("metadata", proto_note.metadata)?; + let metadata: NoteMetadata = proto_note.metadata.decode()?; let details = proto_note .details @@ -230,13 +236,13 @@ impl From for proto::note::NoteHeader { } } +#[grpc_decode] impl TryFrom for NoteHeader { type Error = ConversionError; fn try_from(value: proto::note::NoteHeader) -> Result { - let decoder = value.decoder(); - let note_id_word: Word = decoder.decode_field("note_id", value.note_id)?; - let metadata: NoteMetadata = decoder.decode_field("metadata", value.metadata)?; + let note_id_word: Word = value.note_id.decode()?; + let metadata: NoteMetadata = value.metadata.decode()?; Ok(NoteHeader::new(NoteId::from_raw(note_id_word), metadata)) } diff --git a/crates/proto/src/domain/nullifier.rs b/crates/proto/src/domain/nullifier.rs index 1a6712bdfc..675d127ff3 100644 --- a/crates/proto/src/domain/nullifier.rs +++ b/crates/proto/src/domain/nullifier.rs @@ -2,7 +2,7 @@ use miden_protocol::Word; use miden_protocol::crypto::merkle::smt::SmtProof; use miden_protocol::note::Nullifier; -use crate::errors::{ConversionError, GrpcDecodeExt}; +use crate::errors::{ConversionError, GrpcDecodeExt as _, grpc_decode}; use crate::generated as proto; // FROM NULLIFIER @@ -41,16 +41,16 @@ pub struct NullifierWitnessRecord { pub proof: SmtProof, } +#[grpc_decode] impl TryFrom for NullifierWitnessRecord { type Error = ConversionError; fn try_from( nullifier_witness_record: proto::store::block_inputs::NullifierWitness, ) -> Result { - let decoder = nullifier_witness_record.decoder(); Ok(Self { - nullifier: decoder.decode_field("nullifier", nullifier_witness_record.nullifier)?, - proof: decoder.decode_field("opening", nullifier_witness_record.opening)?, + nullifier: nullifier_witness_record.nullifier.decode()?, + proof: nullifier_witness_record.opening.decode()?, }) } } diff --git a/crates/proto/src/domain/transaction.rs b/crates/proto/src/domain/transaction.rs index 79cbe23be8..824236417a 100644 --- a/crates/proto/src/domain/transaction.rs +++ b/crates/proto/src/domain/transaction.rs @@ -3,7 +3,13 @@ use miden_protocol::note::Nullifier; use miden_protocol::transaction::{InputNoteCommitment, TransactionId}; use miden_protocol::utils::Serializable; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; +use crate::errors::{ + ConversionError, + ConversionResultExt, + DecodeBytesExt, + GrpcDecodeExt as _, + grpc_decode, +}; use crate::generated as proto; // FROM TRANSACTION ID @@ -45,12 +51,12 @@ impl TryFrom for TransactionId { } } +#[grpc_decode] impl TryFrom for TransactionId { type Error = ConversionError; fn try_from(value: proto::transaction::TransactionId) -> Result { - let decoder = value.decoder(); - decoder.decode_field("id", value.id) + value.id.decode() } } @@ -66,12 +72,12 @@ impl From for proto::transaction::InputNoteCommitment { } } +#[grpc_decode] impl TryFrom for InputNoteCommitment { type Error = ConversionError; fn try_from(value: proto::transaction::InputNoteCommitment) -> Result { - let decoder = value.decoder(); - let nullifier: Nullifier = decoder.decode_field("nullifier", value.nullifier)?; + let nullifier: Nullifier = value.nullifier.decode()?; let header: Option = value.header.map(TryInto::try_into).transpose().context("header")?; diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 341d4ed6a5..0315ebfab0 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -3,7 +3,7 @@ use std::fmt; use std::marker::PhantomData; // Re-export the GrpcError derive macro for convenience -pub use miden_node_grpc_error_macro::GrpcError; +pub use miden_node_grpc_error_macro::{GrpcError, grpc_decode}; use miden_protocol::utils::DeserializationError; #[cfg(test)] From 3ba4358b850fe9234867b56d9f7a70e45f2531fb Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 18 Mar 2026 13:48:43 +1300 Subject: [PATCH 25/26] update macro for more use casese --- crates/block-producer/src/store/mod.rs | 3 +- crates/grpc-error-macro/Cargo.toml | 2 +- crates/grpc-error-macro/src/lib.rs | 237 ++++++++++++++++++++++--- crates/proto/src/domain/account.rs | 19 +- crates/proto/src/domain/mempool.rs | 15 +- 5 files changed, 233 insertions(+), 43 deletions(-) diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index bfdff4b8cc..df9cbcdd51 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -80,8 +80,7 @@ impl TryFrom for TransactionInputs { let mut nullifiers = HashMap::new(); for nullifier_record in response.nullifiers { - let decoder = nullifier_record.decoder(); - let nullifier = decoder.decode_field("nullifier", nullifier_record.nullifier)?; + let nullifier = nullifier_record.nullifier.decode()?; // Note that this intentionally maps 0 to None as this is the definition used in // protobuf. diff --git a/crates/grpc-error-macro/Cargo.toml b/crates/grpc-error-macro/Cargo.toml index 8431376845..555ff61348 100644 --- a/crates/grpc-error-macro/Cargo.toml +++ b/crates/grpc-error-macro/Cargo.toml @@ -19,4 +19,4 @@ proc-macro = true [dependencies] quote = "1.0" -syn = { features = ["full", "visit-mut"], version = "2.0" } +syn = { features = ["full", "visit", "visit-mut"], version = "2.0" } diff --git a/crates/grpc-error-macro/src/lib.rs b/crates/grpc-error-macro/src/lib.rs index 2a491f0d05..024da3d15a 100644 --- a/crates/grpc-error-macro/src/lib.rs +++ b/crates/grpc-error-macro/src/lib.rs @@ -192,7 +192,8 @@ pub fn derive_grpc_error(input: TokenStream) -> TokenStream { /// 3. Rewrite each `.decode()` call to `decoder.decode_field("field", .field)` /// /// The field name string is extracted from the last segment of the field access expression. -/// Calls inside closures are **not** rewritten (closures have their own receiver scope). +/// `.decode()` calls inside closures, for-loops, and match arms are also rewritten +/// when the receiver is rooted at the closure parameter, loop variable, or match binding. /// /// # Example /// @@ -242,9 +243,8 @@ fn process_method(method: &mut syn::ImplItemFn) { }; let mut rewriter = DecodeRewriter { - param_name: param_name.clone(), + roots: vec![param_name.clone()], found_decode: false, - closure_depth: 0, }; syn::visit_mut::visit_block_mut(&mut rewriter, &mut method.block); @@ -269,24 +269,70 @@ fn extract_param_name(sig: &syn::Signature) -> Option { None } -/// AST visitor that rewrites `.field.decode()` → -/// `decoder.decode_field("field", .field)`. +/// Extracts all ident bindings from a pattern (e.g., `Foo::Bar(x)` → `[x]`). +fn extract_pat_idents(pat: &syn::Pat) -> Vec { + let mut idents = Vec::new(); + collect_pat_idents(pat, &mut idents); + idents +} + +fn collect_pat_idents(pat: &syn::Pat, idents: &mut Vec) { + match pat { + syn::Pat::Ident(pat_ident) => { + idents.push(pat_ident.ident.clone()); + }, + syn::Pat::TupleStruct(tuple_struct) => { + for elem in &tuple_struct.elems { + collect_pat_idents(elem, idents); + } + }, + syn::Pat::Tuple(tuple) => { + for elem in &tuple.elems { + collect_pat_idents(elem, idents); + } + }, + syn::Pat::Struct(pat_struct) => { + for field in &pat_struct.fields { + collect_pat_idents(&field.pat, idents); + } + }, + syn::Pat::Reference(pat_ref) => { + collect_pat_idents(&pat_ref.pat, idents); + }, + _ => {}, + } +} + +/// Injects `let decoder = .decoder();` at the start of a block. +fn inject_decoder_into_block(block: &mut syn::Block, root: &Ident) { + let decoder_stmt: syn::Stmt = syn::parse_quote! { + let decoder = #root.decoder(); + }; + block.stmts.insert(0, decoder_stmt); +} + +/// AST visitor that rewrites `.field.decode()` → +/// `decoder.decode_field("field", .field)`. +/// +/// Tracks a stack of "roots" — variable names that are valid decode receivers. +/// The function parameter is the initial root. Closure parameters, for-loop +/// variables, and match bindings are pushed as temporary roots when visiting +/// their respective scopes. struct DecodeRewriter { - /// The function parameter name (e.g., `value`, `response`, `from`). - param_name: Ident, - /// Whether any `.decode()` calls were found and rewritten. + /// Stack of decode root variable names. + roots: Vec, + /// Whether any `.decode()` calls were found and rewritten at the top-level + /// (function parameter) scope. found_decode: bool, - /// Current closure nesting depth — skip rewriting when > 0. - closure_depth: u32, } impl DecodeRewriter { - /// Checks if a field access expression is rooted at the function parameter, + /// Checks if a field access expression is rooted at any known root, /// and returns the last field name segment if so. fn extract_field_info(&self, expr: &syn::Expr) -> Option { if let syn::Expr::Field(field_expr) = expr { if let syn::Member::Named(field_ident) = &field_expr.member { - if self.root_is_param(expr) { + if self.root_matches_any(expr) { return Some(field_ident.clone()); } } @@ -294,14 +340,57 @@ impl DecodeRewriter { None } - /// Recursively checks if the root of a field access chain is the function parameter. - fn root_is_param(&self, expr: &syn::Expr) -> bool { + /// Recursively checks if the root of a field access chain matches any known root. + fn root_matches_any(&self, expr: &syn::Expr) -> bool { match expr { - syn::Expr::Path(path) => path.path.is_ident(&self.param_name), - syn::Expr::Field(field) => self.root_is_param(&field.base), + syn::Expr::Path(path) => self.roots.iter().any(|root| path.path.is_ident(root)), + syn::Expr::Field(field) => self.root_matches_any(&field.base), _ => false, } } + + /// Finds which root an expression is rooted at. + fn find_root(&self, expr: &syn::Expr) -> Option<&Ident> { + match expr { + syn::Expr::Path(path) => self.roots.iter().find(|root| path.path.is_ident(&**root)), + syn::Expr::Field(field) => self.find_root(&field.base), + _ => None, + } + } +} + +/// Visitor that detects `decoder.decode_field(...)` calls in the immediate scope, +/// without recursing into nested closures or for-loops (which have their own decoders). +struct DecoderCallFinder(bool); + +impl<'ast> syn::visit::Visit<'ast> for DecoderCallFinder { + fn visit_expr_method_call(&mut self, mc: &'ast syn::ExprMethodCall) { + if mc.method == "decode_field" { + if let syn::Expr::Path(path) = &*mc.receiver { + if path.path.is_ident("decoder") { + self.0 = true; + } + } + } + syn::visit::visit_expr_method_call(self, mc); + } + + fn visit_expr_closure(&mut self, _: &'ast syn::ExprClosure) {} + fn visit_expr_for_loop(&mut self, _: &'ast syn::ExprForLoop) {} +} + +/// Checks if an expression contains any `decoder.decode_field(...)` calls. +fn expr_contains_decoder_call(expr: &syn::Expr) -> bool { + let mut finder = DecoderCallFinder(false); + syn::visit::Visit::visit_expr(&mut finder, expr); + finder.0 +} + +/// Checks if a block contains any `decoder.decode_field(...)` calls. +fn block_contains_decoder_call(block: &syn::Block) -> bool { + let mut finder = DecoderCallFinder(false); + syn::visit::Visit::visit_block(&mut finder, block); + finder.0 } impl VisitMut for DecodeRewriter { @@ -309,18 +398,16 @@ impl VisitMut for DecodeRewriter { // Recurse into children first (bottom-up). syn::visit_mut::visit_expr_mut(self, expr); - // Skip rewriting inside closures. - if self.closure_depth > 0 { - return; - } - // Match: .decode() with no arguments. if let syn::Expr::MethodCall(mc) = expr { if mc.method == "decode" && mc.args.is_empty() { if let Some(field_name) = self.extract_field_info(&mc.receiver) { + let root = self.find_root(&mc.receiver).cloned(); let receiver = &mc.receiver; let name_str = field_name.to_string(); - self.found_decode = true; + if root.as_ref() == self.roots.first() { + self.found_decode = true; + } *expr = syn::parse_quote! { decoder.decode_field(#name_str, #receiver) }; @@ -330,8 +417,110 @@ impl VisitMut for DecodeRewriter { } fn visit_expr_closure_mut(&mut self, closure: &mut syn::ExprClosure) { - self.closure_depth += 1; + // Extract closure parameter idents as new roots. + let new_roots: Vec = closure + .inputs + .iter() + .filter_map(|pat| { + if let syn::Pat::Ident(pat_ident) = pat { + Some(pat_ident.ident.clone()) + } else { + None + } + }) + .collect(); + + if new_roots.is_empty() { + // No usable closure params — visit normally without adding roots. + syn::visit_mut::visit_expr_closure_mut(self, closure); + return; + } + + // If the closure body is a block, we can inject a decoder statement. + // If it's an expression, wrap it in a block first. + let roots_start = self.roots.len(); + self.roots.extend(new_roots); + syn::visit_mut::visit_expr_closure_mut(self, closure); - self.closure_depth -= 1; + + // Check if we need to inject a decoder. Get the root that was used. + let injected_roots: Vec = self.roots[roots_start..].to_vec(); + self.roots.truncate(roots_start); + + // Find which root needs a decoder by checking the closure body. + // If the body is a block expression, check it directly. + // If the body is a non-block expression (e.g., single-expression closure), + // check if the rewritten expression contains decoder.decode_field calls + // and wrap it in a block with the decoder statement. + for root in &injected_roots { + let needs_decoder = match &*closure.body { + syn::Expr::Block(expr_block) => block_contains_decoder_call(&expr_block.block), + other => expr_contains_decoder_call(other), + }; + + if needs_decoder { + if let syn::Expr::Block(expr_block) = &mut *closure.body { + inject_decoder_into_block(&mut expr_block.block, root); + } else { + // Wrap the expression body in a block with a decoder statement. + let body = &closure.body; + *closure.body = syn::parse_quote! { + { + let decoder = #root.decoder(); + #body + } + }; + } + break; + } + } + } + + fn visit_expr_for_loop_mut(&mut self, for_loop: &mut syn::ExprForLoop) { + // Extract the loop variable as a new root. + let new_roots = extract_pat_idents(&for_loop.pat); + + let roots_start = self.roots.len(); + self.roots.extend(new_roots); + + syn::visit_mut::visit_expr_for_loop_mut(self, for_loop); + + let injected_roots: Vec = self.roots[roots_start..].to_vec(); + self.roots.truncate(roots_start); + + for root in &injected_roots { + if block_contains_decoder_call(&for_loop.body) { + inject_decoder_into_block(&mut for_loop.body, root); + break; // Only one decoder per block + } + } + } + + fn visit_arm_mut(&mut self, arm: &mut syn::Arm) { + // Extract match binding idents as new roots. + let new_roots = extract_pat_idents(&arm.pat); + + let roots_start = self.roots.len(); + self.roots.extend(new_roots); + + syn::visit_mut::visit_arm_mut(self, arm); + + let injected_roots: Vec = self.roots[roots_start..].to_vec(); + self.roots.truncate(roots_start); + + // Inject decoder into the arm body if it's a block expression. + for root in &injected_roots { + let needs_decoder = match &*arm.body { + syn::Expr::Block(expr_block) => block_contains_decoder_call(&expr_block.block), + _ => false, + }; + + if needs_decoder { + if let syn::Expr::Block(expr_block) = &mut *arm.body { + inject_decoder_into_block(&mut expr_block.block, root); + break; + } + } + } } } diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 163751c401..f0988c2d41 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -115,6 +115,7 @@ impl From<&AccountInfo> for proto::account::AccountDetails { // ACCOUNT STORAGE HEADER //================================================================================================ +#[grpc_decode] impl TryFrom for AccountStorageHeader { type Error = ConversionError; @@ -124,10 +125,9 @@ impl TryFrom for AccountStorageHeader { let slot_headers = slots .into_iter() .map(|slot| { - let decoder = slot.decoder(); let slot_name = StorageSlotName::new(slot.slot_name)?; let slot_type = storage_slot_type_from_raw(slot.slot_type)?; - let commitment = decoder.decode_field("commitment", slot.commitment)?; + let commitment = slot.commitment.decode()?; Ok(StorageSlotHeader::new(slot_name, slot_type, commitment)) }) .collect::, ConversionError>>() @@ -348,6 +348,7 @@ impl AccountVaultDetails { } } +#[grpc_decode] impl TryFrom for AccountVaultDetails { type Error = ConversionError; @@ -359,8 +360,7 @@ impl TryFrom for AccountVaultDetails { } else { let parsed_assets = Result::, ConversionError>::from_iter(assets.into_iter().map(|asset| { - let decoder = asset.decoder(); - let word: Word = decoder.decode_field("asset", asset.asset)?; + let word: Word = asset.asset.decode()?; Asset::try_from(word).map_err(ConversionError::from) })) .context("assets")?; @@ -496,6 +496,7 @@ impl AccountStorageMapDetails { } } +#[grpc_decode] impl TryFrom for AccountStorageMapDetails { @@ -531,9 +532,8 @@ impl TryFrom let entries = entries .into_iter() .map(|entry| { - let decoder = entry.decoder(); - let key = StorageMapKey::new(decoder.decode_field("key", entry.key)?); - let value = decoder.decode_field("value", entry.value)?; + let key = StorageMapKey::new(entry.key.decode()?); + let value = entry.value.decode()?; Ok((key, value)) }) .collect::, ConversionError>>() @@ -543,10 +543,7 @@ impl TryFrom Some(ProtoEntries::EntriesWithProofs(MapEntriesWithProofs { entries })) => { let proofs = entries .into_iter() - .map(|entry| { - let decoder = entry.decoder(); - decoder.decode_field("proof", entry.proof) - }) + .map(|entry| entry.proof.decode()) .collect::, ConversionError>>() .context("entries")?; StorageMapEntries::EntriesWithProofs(proofs) diff --git a/crates/proto/src/domain/mempool.rs b/crates/proto/src/domain/mempool.rs index e5cde04f35..e87336d5b0 100644 --- a/crates/proto/src/domain/mempool.rs +++ b/crates/proto/src/domain/mempool.rs @@ -7,7 +7,13 @@ use miden_protocol::transaction::TransactionId; use miden_protocol::utils::Serializable; use miden_standards::note::AccountTargetNetworkNote; -use crate::errors::{ConversionError, ConversionResultExt, DecodeBytesExt, GrpcDecodeExt}; +use crate::errors::{ + ConversionError, + ConversionResultExt, + DecodeBytesExt, + GrpcDecodeExt as _, + grpc_decode, +}; use crate::generated as proto; #[derive(Debug, Clone)] @@ -78,6 +84,7 @@ impl From for proto::block_producer::MempoolEvent { } } +#[grpc_decode] impl TryFrom for MempoolEvent { type Error = ConversionError; @@ -88,8 +95,7 @@ impl TryFrom for MempoolEvent { match event { proto::block_producer::mempool_event::Event::TransactionAdded(tx) => { - let decoder = tx.decoder(); - let id = decoder.decode_field("id", tx.id)?; + let id = tx.id.decode()?; let nullifiers = tx .nullifiers .into_iter() @@ -116,8 +122,7 @@ impl TryFrom for MempoolEvent { }) }, proto::block_producer::mempool_event::Event::BlockCommitted(block_committed) => { - let decoder = block_committed.decoder(); - let header = decoder.decode_field("block_header", block_committed.block_header)?; + let header = block_committed.block_header.decode()?; let header = Box::new(header); let txs = block_committed .transactions From 6c4951be66c149f9e6daa518882dd979a429930c Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 18 Mar 2026 14:54:05 +1300 Subject: [PATCH 26/26] Add macro tests --- crates/proto/src/domain/account.rs | 1 + crates/proto/src/errors/mod.rs | 2 + crates/proto/src/errors/test_grpc_decode.rs | 229 ++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 crates/proto/src/errors/test_grpc_decode.rs diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index f0988c2d41..7bdf1721b1 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -168,6 +168,7 @@ pub struct AccountDetailRequest { pub storage_requests: Vec, } +#[grpc_decode] impl TryFrom for AccountDetailRequest { type Error = ConversionError; diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 0315ebfab0..c073e2da90 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -6,6 +6,8 @@ use std::marker::PhantomData; pub use miden_node_grpc_error_macro::{GrpcError, grpc_decode}; use miden_protocol::utils::DeserializationError; +#[cfg(test)] +mod test_grpc_decode; #[cfg(test)] mod test_macro; diff --git a/crates/proto/src/errors/test_grpc_decode.rs b/crates/proto/src/errors/test_grpc_decode.rs new file mode 100644 index 0000000000..cf623d0028 --- /dev/null +++ b/crates/proto/src/errors/test_grpc_decode.rs @@ -0,0 +1,229 @@ +//! Tests for the `#[grpc_decode]` attribute macro. +//! +//! Verifies that the macro correctly rewrites `.decode()` calls in function parameters, +//! closures, for-loops, and match arms. + +use miden_protocol::{Felt, Word}; + +use crate::errors::{ConversionError, GrpcDecodeExt as _, grpc_decode}; +use crate::generated as proto; + +// HELPER +// ================================================================================================ + +/// Creates a valid `proto::primitives::Digest` that converts to `[Felt; 4]` without error. +fn valid_digest() -> proto::primitives::Digest { + proto::primitives::Digest { d0: 1, d1: 2, d2: 3, d3: 4 } +} + +// FUNCTION PARAMETER +// ================================================================================================ + +/// Wrapper to test basic function parameter decoding. +#[derive(Debug)] +struct DecodedEntry { + key: Word, + value: Word, +} + +#[grpc_decode] +impl TryFrom for DecodedEntry { + type Error = ConversionError; + + fn try_from(entry: proto::primitives::SmtLeafEntry) -> Result { + let key: Word = entry.key.decode()?; + let value: Word = entry.value.decode()?; + Ok(Self { key, value }) + } +} + +#[test] +fn test_function_param_decode() { + let entry = proto::primitives::SmtLeafEntry { + key: Some(valid_digest()), + value: Some(valid_digest()), + }; + let decoded = DecodedEntry::try_from(entry).unwrap(); + assert_eq!(decoded.key[0], Felt::new(1)); + assert_eq!(decoded.value[0], Felt::new(1)); +} + +#[test] +fn test_function_param_missing_field() { + let entry = proto::primitives::SmtLeafEntry { key: None, value: Some(valid_digest()) }; + let err = DecodedEntry::try_from(entry).unwrap_err(); + assert!( + err.to_string().contains("key") && err.to_string().contains("missing"), + "expected missing key error, got: {err}", + ); +} + +// CLOSURE +// ================================================================================================ + +/// Wrapper to test closure-based decoding (`.map(|item| item.field.decode())`). +#[derive(Debug)] +struct DecodedEntries { + keys: Vec, +} + +#[grpc_decode] +impl TryFrom for DecodedEntries { + type Error = ConversionError; + + fn try_from(value: proto::primitives::SmtLeafEntryList) -> Result { + let keys: Vec = value + .entries + .into_iter() + .map(|entry| { + let key: Word = entry.key.decode()?; + Ok(key) + }) + .collect::>()?; + Ok(Self { keys }) + } +} + +#[test] +fn test_closure_decode() { + let entries = proto::primitives::SmtLeafEntryList { + entries: vec![ + proto::primitives::SmtLeafEntry { + key: Some(valid_digest()), + value: Some(valid_digest()), + }, + proto::primitives::SmtLeafEntry { + key: Some(proto::primitives::Digest { d0: 10, d1: 20, d2: 30, d3: 40 }), + value: Some(valid_digest()), + }, + ], + }; + let decoded = DecodedEntries::try_from(entries).unwrap(); + assert_eq!(decoded.keys.len(), 2); + assert_eq!(decoded.keys[0][0], Felt::new(1)); + assert_eq!(decoded.keys[1][0], Felt::new(10)); +} + +#[test] +fn test_closure_decode_missing_field() { + let entries = proto::primitives::SmtLeafEntryList { + entries: vec![proto::primitives::SmtLeafEntry { key: None, value: Some(valid_digest()) }], + }; + let err = DecodedEntries::try_from(entries).unwrap_err(); + assert!( + err.to_string().contains("key") && err.to_string().contains("missing"), + "expected missing key error, got: {err}", + ); +} + +// FOR-LOOP +// ================================================================================================ + +/// Wrapper to test for-loop decoding. +#[derive(Debug)] +struct CollectedKeys { + keys: Vec, +} + +#[grpc_decode] +impl TryFrom for CollectedKeys { + type Error = ConversionError; + + fn try_from(value: proto::primitives::SmtLeafEntryList) -> Result { + let mut keys = Vec::new(); + for entry in value.entries { + let key: Word = entry.key.decode()?; + keys.push(key); + } + Ok(Self { keys }) + } +} + +#[test] +fn test_for_loop_decode() { + let entries = proto::primitives::SmtLeafEntryList { + entries: vec![ + proto::primitives::SmtLeafEntry { + key: Some(valid_digest()), + value: Some(valid_digest()), + }, + proto::primitives::SmtLeafEntry { + key: Some(proto::primitives::Digest { d0: 5, d1: 6, d2: 7, d3: 8 }), + value: Some(valid_digest()), + }, + ], + }; + let decoded = CollectedKeys::try_from(entries).unwrap(); + assert_eq!(decoded.keys.len(), 2); + assert_eq!(decoded.keys[0][0], Felt::new(1)); + assert_eq!(decoded.keys[1][0], Felt::new(5)); +} + +#[test] +fn test_for_loop_decode_missing_field() { + let entries = proto::primitives::SmtLeafEntryList { + entries: vec![proto::primitives::SmtLeafEntry { key: None, value: None }], + }; + let err = CollectedKeys::try_from(entries).unwrap_err(); + assert!( + err.to_string().contains("key") && err.to_string().contains("missing"), + "expected missing key error, got: {err}", + ); +} + +// MATCH ARM +// ================================================================================================ + +/// Test enum to exercise match-arm decoding. +enum TestInput { + WithKey(proto::primitives::SmtLeafEntry), + Empty, +} + +/// Wrapper for match-arm decode results. +#[derive(Debug)] +struct MatchResult { + key: Option, +} + +#[grpc_decode] +impl TryFrom for MatchResult { + type Error = ConversionError; + + fn try_from(value: TestInput) -> Result { + match value { + TestInput::WithKey(entry) => { + let key: Word = entry.key.decode()?; + Ok(Self { key: Some(key) }) + }, + TestInput::Empty => Ok(Self { key: None }), + } + } +} + +#[test] +fn test_match_arm_decode() { + let input = TestInput::WithKey(proto::primitives::SmtLeafEntry { + key: Some(valid_digest()), + value: None, + }); + let result = MatchResult::try_from(input).unwrap(); + assert_eq!(result.key.unwrap()[0], Felt::new(1)); +} + +#[test] +fn test_match_arm_empty_variant() { + let input = TestInput::Empty; + let result = MatchResult::try_from(input).unwrap(); + assert!(result.key.is_none()); +} + +#[test] +fn test_match_arm_missing_field() { + let input = TestInput::WithKey(proto::primitives::SmtLeafEntry { key: None, value: None }); + let err = MatchResult::try_from(input).unwrap_err(); + assert!( + err.to_string().contains("key") && err.to_string().contains("missing"), + "expected missing key error, got: {err}", + ); +}