use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockHeader;
use miden_protocol::crypto::merkle::MerklePath;
use miden_protocol::note::{
Note,
NoteAttachmentHeader,
NoteAttachmentScheme,
NoteAttachments,
NoteDetails,
NoteDetailsCommitment,
NoteHeader,
NoteId,
NoteInclusionProof,
NoteMetadata,
NoteScript,
NoteTag,
NoteType,
PartialNoteMetadata,
};
use miden_protocol::{MastForest, MastNodeId, Word};
use miden_tx::utils::serde::Deserializable;
use super::{MissingFieldHelper, RpcConversionError};
use crate::rpc::{RpcError, generated as proto};
impl From<NoteId> for proto::note::NoteId {
fn from(value: NoteId) -> Self {
proto::note::NoteId { id: Some(value.into()) }
}
}
impl TryFrom<proto::note::NoteId> for NoteId {
type Error = RpcConversionError;
fn try_from(value: proto::note::NoteId) -> Result<Self, Self::Error> {
let word =
Word::try_from(value.id.ok_or(proto::note::NoteId::missing_field(stringify!(id)))?)?;
Ok(Self::from_raw(word))
}
}
fn note_type_from_proto(raw: i32) -> Result<NoteType, RpcConversionError> {
let proto_note_type = proto::note::NoteType::try_from(raw)
.map_err(|_| RpcConversionError::InvalidField(alloc::format!("note_type={raw}")))?;
match proto_note_type {
proto::note::NoteType::Public => Ok(NoteType::Public),
proto::note::NoteType::Private => Ok(NoteType::Private),
proto::note::NoteType::Unspecified => {
Err(RpcConversionError::InvalidField("note_type=NOTE_TYPE_UNSPECIFIED".into()))
},
}
}
fn note_type_to_proto(note_type: NoteType) -> i32 {
let proto_note_type = match note_type {
NoteType::Public => proto::note::NoteType::Public,
NoteType::Private => proto::note::NoteType::Private,
};
proto_note_type as i32
}
fn attachment_headers_from_proto(
schemes: &[u32],
) -> Result<[NoteAttachmentHeader; NoteAttachments::MAX_COUNT], RpcConversionError> {
if schemes.len() > NoteAttachments::MAX_COUNT {
return Err(RpcConversionError::InvalidField(alloc::format!(
"attachment_schemes length {} exceeds NoteAttachments::MAX_COUNT",
schemes.len(),
)));
}
let mut headers = [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT];
for (slot, raw) in schemes.iter().enumerate() {
if *raw == 0 {
continue;
}
let raw_u16 = u16::try_from(*raw).map_err(|_| {
RpcConversionError::InvalidField(alloc::format!(
"attachment_schemes[{slot}]={raw} does not fit in u16",
))
})?;
let scheme = NoteAttachmentScheme::new(raw_u16).map_err(|err| {
RpcConversionError::InvalidField(alloc::format!("attachment_schemes[{slot}]: {err}"))
})?;
headers[slot] = NoteAttachmentHeader::new(scheme);
}
Ok(headers)
}
fn attachment_schemes_to_proto(
headers: &[NoteAttachmentHeader; NoteAttachments::MAX_COUNT],
) -> Vec<u32> {
let mut encoded: Vec<u32> = headers
.iter()
.map(|h| h.scheme().map_or(0, |s| u32::from(s.as_u16())))
.collect();
while matches!(encoded.last(), Some(0)) {
encoded.pop();
}
encoded
}
impl TryFrom<proto::note::NoteMetadata> for NoteMetadata {
type Error = RpcConversionError;
fn try_from(value: proto::note::NoteMetadata) -> Result<Self, Self::Error> {
let partial_metadata: PartialNoteMetadata = (&value).try_into()?;
let attachment_headers = attachment_headers_from_proto(&value.attachment_schemes)?;
let attachments_commitment = value
.attachments_commitment
.ok_or_else(|| {
proto::note::NoteMetadata::missing_field(stringify!(attachments_commitment))
})?
.try_into()?;
Ok(NoteMetadata::from_parts(
partial_metadata,
attachment_headers,
attachments_commitment,
))
}
}
impl TryFrom<&proto::note::NoteMetadata> for PartialNoteMetadata {
type Error = RpcConversionError;
fn try_from(value: &proto::note::NoteMetadata) -> Result<Self, Self::Error> {
let sender = value
.sender
.clone()
.ok_or_else(|| proto::note::NoteMetadata::missing_field(stringify!(sender)))?
.try_into()?;
let note_type = note_type_from_proto(value.note_type)?;
let tag = NoteTag::new(value.tag);
Ok(PartialNoteMetadata::new(sender, note_type).with_tag(tag))
}
}
impl From<NoteMetadata> for proto::note::NoteMetadata {
fn from(value: NoteMetadata) -> Self {
proto::note::NoteMetadata {
sender: Some(value.sender().into()),
note_type: note_type_to_proto(value.note_type()),
tag: value.tag().as_u32(),
attachment_schemes: attachment_schemes_to_proto(value.attachment_headers()),
attachments_commitment: Some(value.attachments_commitment().into()),
}
}
}
impl TryFrom<proto::note::NoteHeader> for NoteHeader {
type Error = RpcConversionError;
fn try_from(value: proto::note::NoteHeader) -> Result<Self, Self::Error> {
let details_commitment_word: Word = value
.details_commitment
.ok_or(proto::note::NoteHeader::missing_field(stringify!(details_commitment)))?
.try_into()?;
let metadata = value
.metadata
.ok_or(proto::note::NoteHeader::missing_field(stringify!(metadata)))?
.try_into()?;
Ok(NoteHeader::new(
NoteDetailsCommitment::from_raw(details_commitment_word),
metadata,
))
}
}
impl TryFrom<proto::note::NoteInclusionInBlockProof> for NoteInclusionProof {
type Error = RpcConversionError;
fn try_from(value: proto::note::NoteInclusionInBlockProof) -> Result<Self, Self::Error> {
Ok(NoteInclusionProof::new(
value.block_num.into(),
u16::try_from(value.note_index_in_block)
.map_err(|_| RpcConversionError::InvalidField("NoteIndexInBlock".into()))?,
value
.inclusion_path
.ok_or_else(|| {
proto::note::NoteInclusionInBlockProof::missing_field(stringify!(
inclusion_path
))
})?
.try_into()?,
)?)
}
}
#[derive(Debug, Clone)]
pub struct NoteSyncBlock {
pub block_header: BlockHeader,
pub mmr_path: MerklePath,
pub notes: BTreeMap<NoteId, CommittedNote>,
}
#[allow(clippy::large_enum_variant)]
pub enum SyncedNoteDetails {
Public(Note),
Private(Option<NoteAttachments>),
}
impl TryFrom<proto::rpc::sync_notes_response::NoteSyncBlock> for NoteSyncBlock {
type Error = RpcError;
fn try_from(
block: proto::rpc::sync_notes_response::NoteSyncBlock,
) -> Result<Self, Self::Error> {
let block_header = block
.block_header
.ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(blocks.block_header)))?
.try_into()?;
let mmr_path = block
.mmr_path
.ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(blocks.mmr_path)))?
.try_into()?;
let notes: BTreeMap<NoteId, CommittedNote> = block
.notes
.into_iter()
.map(|n| {
let note = CommittedNote::try_from(n)?;
Ok((*note.note_id(), note))
})
.collect::<Result<_, RpcConversionError>>()?;
Ok(NoteSyncBlock { block_header, mmr_path, notes })
}
}
#[derive(Debug, Clone)]
pub struct CommittedNote {
note_id: NoteId,
metadata: NoteMetadata,
inclusion_proof: NoteInclusionProof,
}
impl CommittedNote {
pub fn new(
note_id: NoteId,
metadata: NoteMetadata,
inclusion_proof: NoteInclusionProof,
) -> Self {
Self { note_id, metadata, inclusion_proof }
}
pub fn note_id(&self) -> &NoteId {
&self.note_id
}
pub fn note_type(&self) -> NoteType {
self.metadata.note_type()
}
pub fn tag(&self) -> NoteTag {
self.metadata.tag()
}
pub fn sender(&self) -> AccountId {
self.metadata.sender()
}
pub fn metadata(&self) -> &NoteMetadata {
&self.metadata
}
pub fn inclusion_proof(&self) -> &NoteInclusionProof {
&self.inclusion_proof
}
}
impl TryFrom<proto::note::NoteSyncRecord> for CommittedNote {
type Error = RpcConversionError;
fn try_from(note: proto::note::NoteSyncRecord) -> Result<Self, Self::Error> {
let proto_metadata = note
.metadata
.ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.metadata)))?;
let metadata: NoteMetadata = proto_metadata.try_into()?;
let proto_inclusion_proof = note.inclusion_proof.ok_or(
proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.inclusion_proof)),
)?;
let note_id: NoteId = proto_inclusion_proof
.note_id
.ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
notes.inclusion_proof.note_id
)))?
.try_into()?;
let inclusion_proof: NoteInclusionProof = proto_inclusion_proof.try_into()?;
Ok(CommittedNote::new(note_id, metadata, inclusion_proof))
}
}
#[allow(clippy::large_enum_variant)]
pub enum FetchedNote {
Private(NoteId, NoteMetadata, NoteAttachments, NoteInclusionProof),
Public(Note, NoteInclusionProof),
}
impl FetchedNote {
pub fn inclusion_proof(&self) -> &NoteInclusionProof {
match self {
FetchedNote::Private(_, _, _, inclusion_proof)
| FetchedNote::Public(_, inclusion_proof) => inclusion_proof,
}
}
pub fn metadata(&self) -> &NoteMetadata {
match self {
FetchedNote::Private(_, metadata, ..) => metadata,
FetchedNote::Public(note, _) => note.metadata(),
}
}
pub fn attachments(&self) -> &NoteAttachments {
match self {
FetchedNote::Private(_, _, attachments, _) => attachments,
FetchedNote::Public(note, _) => note.attachments(),
}
}
pub fn id(&self) -> NoteId {
match self {
FetchedNote::Private(note_id, ..) => *note_id,
FetchedNote::Public(note, _) => note.id(),
}
}
}
impl TryFrom<proto::note::CommittedNote> for FetchedNote {
type Error = RpcConversionError;
fn try_from(value: proto::note::CommittedNote) -> Result<Self, Self::Error> {
let inclusion_proof = value.inclusion_proof.ok_or_else(|| {
proto::note::CommittedNote::missing_field(stringify!(inclusion_proof))
})?;
let note_id: NoteId = inclusion_proof
.note_id
.ok_or_else(|| {
proto::note::CommittedNote::missing_field(stringify!(inclusion_proof.note_id))
})?
.try_into()?;
let inclusion_proof = NoteInclusionProof::try_from(inclusion_proof)?;
let note = value
.note
.ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note)))?;
let proto_metadata = note
.metadata
.ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note.metadata)))?;
let metadata: NoteMetadata = proto_metadata.clone().try_into()?;
let partial_metadata: PartialNoteMetadata = (&proto_metadata).try_into()?;
let attachments = if note.attachments.is_empty() {
NoteAttachments::empty()
} else {
NoteAttachments::read_from_bytes(¬e.attachments)?
};
if let Some(detail_bytes) = note.details {
let details = NoteDetails::read_from_bytes(&detail_bytes)?;
let (assets, recipient) = details.into_parts();
Ok(FetchedNote::Public(
Note::with_attachments(assets, partial_metadata, recipient, attachments),
inclusion_proof,
))
} else {
Ok(FetchedNote::Private(note_id, metadata, attachments, inclusion_proof))
}
}
}
impl TryFrom<proto::note::NoteScript> for NoteScript {
type Error = RpcConversionError;
fn try_from(note_script: proto::note::NoteScript) -> Result<Self, Self::Error> {
let mast_forest = MastForest::read_from_bytes(¬e_script.mast)?;
let entrypoint = MastNodeId::from_u32_safe(note_script.entrypoint, &mast_forest)?;
Ok(NoteScript::from_parts(alloc::sync::Arc::new(mast_forest), entrypoint))
}
}