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::models::queries::{BlockHeaderCommitment, NetworkAccountType};
43
44#[derive(Debug, thiserror::Error)]
45#[error("failed to convert from database type {from_type} into {into_type}")]
46pub struct DatabaseTypeConversionError {
47    source: Box<dyn std::error::Error + Send + Sync>,
48    from_type: &'static str,
49    into_type: &'static str,
50}
51
52/// Convert from and to it's database representation and back
53///
54/// We do not assume sanity of DB types.
55pub trait SqlTypeConvert: Sized {
56    type Raw: Sized;
57
58    fn to_raw_sql(self) -> Self::Raw;
59    fn from_raw_sql(_raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError>;
60
61    fn map_err<E: std::error::Error + Send + Sync + 'static>(
62        source: E,
63    ) -> DatabaseTypeConversionError {
64        DatabaseTypeConversionError {
65            source: Box::new(source),
66            from_type: std::any::type_name::<Self::Raw>(),
67            into_type: std::any::type_name::<Self>(),
68        }
69    }
70}
71
72impl SqlTypeConvert for BlockHeaderCommitment {
73    type Raw = Vec<u8>;
74    fn from_raw_sql(
75        raw: Self::Raw,
76    ) -> Result<Self, crate::db::models::conv::DatabaseTypeConversionError> {
77        let inner =
78            <Word as Deserializable>::read_from_bytes(raw.as_slice()).map_err(Self::map_err)?;
79        Ok(BlockHeaderCommitment(inner))
80    }
81    fn to_raw_sql(self) -> Self::Raw {
82        self.0.as_bytes().to_vec()
83    }
84}
85
86impl SqlTypeConvert for BlockHeader {
87    type Raw = Vec<u8>;
88
89    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
90        <Self as Deserializable>::read_from_bytes(raw.as_slice()).map_err(Self::map_err)
91    }
92
93    fn to_raw_sql(self) -> Self::Raw {
94        miden_crypto::utils::Serializable::to_bytes(&self)
95    }
96}
97
98impl SqlTypeConvert for NetworkAccountType {
99    type Raw = i32;
100
101    fn to_raw_sql(self) -> Self::Raw {
102        match self {
103            NetworkAccountType::None => 0,
104            NetworkAccountType::Network => 1,
105        }
106    }
107
108    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
109        #[derive(Debug, thiserror::Error)]
110        #[error("invalid network account type value {0}")]
111        struct ValueError(i32);
112
113        match raw {
114            0 => Ok(Self::None),
115            1 => Ok(Self::Network),
116            other => Err(Self::map_err(ValueError(other))),
117        }
118    }
119}
120
121impl SqlTypeConvert for BlockNumber {
122    type Raw = i64;
123
124    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
125        u32::try_from(raw).map(BlockNumber::from).map_err(Self::map_err)
126    }
127
128    fn to_raw_sql(self) -> Self::Raw {
129        i64::from(self.as_u32())
130    }
131}
132
133impl SqlTypeConvert for NoteTag {
134    type Raw = i32;
135
136    #[inline(always)]
137    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
138        #[expect(clippy::cast_sign_loss)]
139        Ok(NoteTag::new(raw as u32))
140    }
141
142    #[inline(always)]
143    fn to_raw_sql(self) -> Self::Raw {
144        self.as_u32() as i32
145    }
146}
147
148impl SqlTypeConvert for StorageSlotType {
149    type Raw = i32;
150
151    #[inline(always)]
152    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
153        #[derive(Debug, thiserror::Error)]
154        #[error("invalid storage slot type value {0}")]
155        struct ValueError(i32);
156
157        Ok(match raw {
158            0 => StorageSlotType::Value,
159            1 => StorageSlotType::Map,
160            invalid => {
161                return Err(Self::map_err(ValueError(invalid)));
162            },
163        })
164    }
165
166    #[inline(always)]
167    fn to_raw_sql(self) -> Self::Raw {
168        match self {
169            StorageSlotType::Value => 0,
170            StorageSlotType::Map => 1,
171        }
172    }
173}
174
175impl SqlTypeConvert for StorageSlotName {
176    type Raw = String;
177
178    fn from_raw_sql(raw: Self::Raw) -> Result<Self, DatabaseTypeConversionError> {
179        StorageSlotName::new(raw).map_err(Self::map_err)
180    }
181
182    fn to_raw_sql(self) -> Self::Raw {
183        String::from(self)
184    }
185}
186
187// Raw type conversions - eventually introduce wrapper types
188// ===========================================================
189
190#[inline(always)]
191pub(crate) fn raw_sql_to_nullifier_prefix(raw: i32) -> u16 {
192    debug_assert!(raw >= 0);
193    raw as u16
194}
195#[inline(always)]
196pub(crate) fn nullifier_prefix_to_raw_sql(prefix: u16) -> i32 {
197    i32::from(prefix)
198}
199
200#[inline(always)]
201pub(crate) fn raw_sql_to_nonce(raw: i64) -> Felt {
202    debug_assert!(raw >= 0);
203    // SAFETY: In the store we write `Felt::as_canonical_u64() as i64`, so `raw` is the bit
204    // reinterpretation of a u64 in the field. Casting back via `raw as u64` recovers that same
205    // canonical value, which is always a valid (already reduced) field element, so
206    // `Felt::new_unchecked` is sound.
207    Felt::new_unchecked(raw as u64)
208}
209#[inline(always)]
210pub(crate) fn nonce_to_raw_sql(nonce: Felt) -> i64 {
211    nonce.as_canonical_u64() as i64
212}
213
214#[inline(always)]
215pub(crate) fn raw_sql_to_fungible_delta(raw: i64) -> i64 {
216    raw
217}
218#[inline(always)]
219pub(crate) fn fungible_delta_to_raw_sql(delta: i64) -> i64 {
220    delta
221}
222
223#[inline(always)]
224#[expect(clippy::cast_sign_loss)]
225pub(crate) fn raw_sql_to_note_type(raw: i32) -> u8 {
226    raw as u8
227}
228#[inline(always)]
229pub(crate) fn note_type_to_raw_sql(note_type: u8) -> i32 {
230    i32::from(note_type)
231}
232
233#[inline(always)]
234pub(crate) fn raw_sql_to_idx(raw: i32) -> usize {
235    raw as usize
236}
237#[inline(always)]
238pub(crate) fn idx_to_raw_sql(idx: usize) -> i32 {
239    idx as i32
240}