From 0efab791d92061535454296b8a39db0ab7178f74 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 6 Mar 2026 08:21:20 +1300 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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,