Skip to main content

miden_node_store/db/models/
conv.rs

1//! Central place to define conversion from and to database primitive types
2//!
3//! Eventually, all of them should have types and we can implement a trait for them
4//! rather than function pairs.
5//!
6//! Notice: All of them are infallible. The invariant is a sane content of the database
7//! and humans ensure the sanity of casts.
8//!
9//! Notice: Keep in mind if you _need_ to expand the datatype, only if you require sorting this is
10//! mandatory!
11//!
12//! Notice: Ensure you understand what casting does at the bit-level before changing any.
13//!
14//! Notice: Changing any of these are _backwards-incompatible_ changes that are not caught/covered
15//! by migrations!
16
17#![expect(
18    clippy::inline_always,
19    reason = "Just unification helpers of 1-2 lines of casting types"
20)]
21#![expect(
22    dead_code,
23    reason = "Not all converters are used bidirectionally, however, keeping them is a good thing"
24)]
25#![expect(
26    clippy::cast_sign_loss,
27    reason = "This is the one file where we map the signed database types to the working types"
28)]
29#![expect(
30    clippy::cast_possible_wrap,
31    reason = "We will not approach the item count where i64 and usize casting will cause issues
32    on relevant platforms"
33)]
34
35use miden_crypto::Word;
36use miden_crypto::utils::Deserializable;
37use miden_protocol::Felt;
38use miden_protocol::account::{StorageSlotName, StorageSlotType};
39use miden_protocol::block::{BlockHeader, BlockNumber};
40use miden_protocol::note::NoteTag;
41
42use crate::db::BlockHeaderCommitment;
43use crate::db::models::queries::NetworkAccountType;
44
45#[derive(Debug, thiserror::Error)]
46#[error("failed to convert from database type {from_type} into {into_type}")]
47pub struct DatabaseTypeConversionError {
48    source: Box<dyn std::error::Error + Send + Sync>,
49    from_type: &'static str,
50    into_type: &'static str,
51}
52
53/// Convert from and to it's database representation and back
54///
55/// We do not assume sanity of DB types.
56pub trait SqlTypeConvert: Sized {
57    type Raw: Sized;
58
59    fn to_raw_sql(self) -> Self::Raw;
60    fn from_raw_sql(_raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError>;
61
62    fn map_err<E: std::error::Error + Send + Sync + 'static>(
63        source: E,
64    ) -> DatabaseTypeConversionError {
65        DatabaseTypeConversionError {
66            source: Box::new(source),
67            from_type: std::any::type_name::<Self::Raw>(),
68            into_type: std::any::type_name::<Self>(),
69        }
70    }
71}
72
73impl SqlTypeConvert for BlockHeaderCommitment {
74    type Raw = Vec<u8>;
75    fn from_raw_sql(
76        raw: Self::Raw,
77    ) -> Result<Self, crate::db::models::conv::DatabaseTypeConversionError> {
78        let inner =
79            <Word as Deserializable>::read_from_bytes(raw.as_slice()).map_err(Self::map_err)?;
80        Ok(BlockHeaderCommitment(inner))
81    }
82    fn to_raw_sql(self) -> Self::Raw {
83        self.0.as_bytes().to_vec()
84    }
85}
86
87impl SqlTypeConvert for BlockHeader {
88    type Raw = Vec<u8>;
89
90    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
91        miden_node_persistence::decode(raw.as_slice()).map_err(Self::map_err)
92    }
93
94    fn to_raw_sql(self) -> Self::Raw {
95        miden_node_persistence::encode(&self)
96    }
97}
98
99impl SqlTypeConvert for NetworkAccountType {
100    type Raw = i32;
101
102    fn to_raw_sql(self) -> Self::Raw {
103        match self {
104            NetworkAccountType::None => 0,
105            NetworkAccountType::Network => 1,
106        }
107    }
108
109    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
110        #[derive(Debug, thiserror::Error)]
111        #[error("invalid network account type value {0}")]
112        struct ValueError(i32);
113
114        match raw {
115            0 => Ok(Self::None),
116            1 => Ok(Self::Network),
117            other => Err(Self::map_err(ValueError(other))),
118        }
119    }
120}
121
122impl SqlTypeConvert for BlockNumber {
123    type Raw = i64;
124
125    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
126        u32::try_from(raw).map(BlockNumber::from).map_err(Self::map_err)
127    }
128
129    fn to_raw_sql(self) -> Self::Raw {
130        i64::from(self.as_u32())
131    }
132}
133
134impl SqlTypeConvert for NoteTag {
135    type Raw = i32;
136
137    #[inline(always)]
138    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
139        #[expect(clippy::cast_sign_loss)]
140        Ok(NoteTag::new(raw as u32))
141    }
142
143    #[inline(always)]
144    fn to_raw_sql(self) -> Self::Raw {
145        self.as_u32() as i32
146    }
147}
148
149impl SqlTypeConvert for StorageSlotType {
150    type Raw = i32;
151
152    #[inline(always)]
153    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
154        #[derive(Debug, thiserror::Error)]
155        #[error("invalid storage slot type value {0}")]
156        struct ValueError(i32);
157
158        Ok(match raw {
159            0 => StorageSlotType::Value,
160            1 => StorageSlotType::Map,
161            invalid => {
162                return Err(Self::map_err(ValueError(invalid)));
163            },
164        })
165    }
166
167    #[inline(always)]
168    fn to_raw_sql(self) -> Self::Raw {
169        match self {
170            StorageSlotType::Value => 0,
171            StorageSlotType::Map => 1,
172        }
173    }
174}
175
176impl SqlTypeConvert for StorageSlotName {
177    type Raw = String;
178
179    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
180        StorageSlotName::new(raw).map_err(Self::map_err)
181    }
182
183    fn to_raw_sql(self) -> Self::Raw {
184        String::from(self)
185    }
186}
187
188// Raw type conversions
189// ================================================================================================
190
191#[inline(always)]
192pub(crate) fn raw_sql_to_nullifier_prefix(raw: i32) -> u16 {
193    debug_assert!(raw >= 0);
194    raw as u16
195}
196#[inline(always)]
197pub(crate) fn nullifier_prefix_to_raw_sql(prefix: u16) -> i32 {
198    i32::from(prefix)
199}
200
201#[inline(always)]
202pub(crate) fn raw_sql_to_nonce(raw: i64) -> Felt {
203    debug_assert!(raw >= 0);
204    // SAFETY: In the store we write `Felt::as_canonical_u64() as i64`, so `raw` is the bit
205    // reinterpretation of a u64 in the field. Casting back via `raw as u64` recovers that same
206    // canonical value, which is always a valid (already reduced) field element, so
207    // `Felt::new_unchecked` is sound.
208    Felt::new_unchecked(raw as u64)
209}
210#[inline(always)]
211pub(crate) fn nonce_to_raw_sql(nonce: Felt) -> i64 {
212    nonce.as_canonical_u64() as i64
213}
214
215#[inline(always)]
216pub(crate) fn raw_sql_to_fungible_delta(raw: i64) -> i64 {
217    raw
218}
219#[inline(always)]
220pub(crate) fn fungible_delta_to_raw_sql(delta: i64) -> i64 {
221    delta
222}
223
224#[inline(always)]
225#[expect(clippy::cast_sign_loss)]
226pub(crate) fn raw_sql_to_note_type(raw: i32) -> u8 {
227    raw as u8
228}
229#[inline(always)]
230pub(crate) fn note_type_to_raw_sql(note_type: u8) -> i32 {
231    i32::from(note_type)
232}
233
234#[inline(always)]
235pub(crate) fn raw_sql_to_idx(raw: i32) -> usize {
236    raw as usize
237}
238#[inline(always)]
239pub(crate) fn idx_to_raw_sql(idx: usize) -> i32 {
240    idx as i32
241}