1use std::collections::{BTreeMap, HashMap};
2use std::num::NonZeroUsize;
3use std::ops::RangeInclusive;
4
5use diesel::prelude::{Queryable, QueryableByName};
6use diesel::query_dsl::methods::SelectDsl;
7use diesel::sqlite::Sqlite;
8use diesel::{
9 BoolExpressionMethods,
10 ExpressionMethods,
11 JoinOnDsl,
12 NullableExpressionMethods,
13 OptionalExtension,
14 QueryDsl,
15 RunQueryDsl,
16 Selectable,
17 SelectableHelper,
18 SqliteConnection,
19};
20use miden_node_proto::domain::account::{AccountInfo, AccountSummary};
21use miden_node_utils::limiter::MAX_RESPONSE_PAYLOAD_BYTES;
22use miden_protocol::Word;
23use miden_protocol::account::{
24 Account,
25 AccountCode,
26 AccountId,
27 AccountStorage,
28 AccountStorageHeader,
29 StorageMap,
30 StorageMapKey,
31 StorageSlot,
32 StorageSlotName,
33 StorageSlotType,
34};
35use miden_protocol::asset::{Asset, AssetId, AssetVault};
36use miden_protocol::block::BlockNumber;
37use miden_protocol::utils::serde::{Deserializable, Serializable};
38
39use crate::db::models::conv::{SqlTypeConvert, raw_sql_to_nonce};
40#[cfg(test)]
41use crate::db::models::vec_raw_try_into;
42use crate::db::{AccountVaultValue, schema};
43use crate::errors::DatabaseError;
44
45type StorageMapValueRow = (i64, String, Vec<u8>, Vec<u8>);
46type StorageHeaderWithEntries =
47 (AccountStorageHeader, HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>);
48
49pub(crate) const VALID_FOREVER: i64 = i64::MAX;
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub(crate) enum NetworkAccountType {
64 None,
66 Network,
68}
69
70pub(crate) fn select_account_code_by_commitment(
85 conn: &mut SqliteConnection,
86 code_commitment: Word,
87) -> Result<Option<Vec<u8>>, DatabaseError> {
88 use schema::account_codes;
89
90 let code_commitment_bytes = code_commitment.to_bytes();
91
92 let result: Option<Vec<u8>> = SelectDsl::select(
93 account_codes::table.filter(account_codes::code_commitment.eq(&code_commitment_bytes)),
94 account_codes::code,
95 )
96 .first(conn)
97 .optional()?;
98
99 Ok(result)
100}
101
102pub(crate) fn select_account(
125 conn: &mut SqliteConnection,
126 account_id: AccountId,
127) -> Result<AccountInfo, DatabaseError> {
128 let raw = SelectDsl::select(schema::accounts::table, AccountSummaryRaw::as_select())
129 .filter(schema::accounts::account_id.eq(account_id.to_bytes()))
130 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
131 .get_result::<AccountSummaryRaw>(conn)
132 .optional()?
133 .ok_or(DatabaseError::AccountNotFoundInDb(account_id))?;
134
135 let summary: AccountSummary = raw.try_into()?;
136
137 let details = if account_id.is_public() {
139 Some(select_full_account(conn, account_id)?)
140 } else {
141 None
142 };
143
144 Ok(AccountInfo { summary, details })
145}
146
147pub(crate) fn select_full_account(
152 conn: &mut SqliteConnection,
153 account_id: AccountId,
154) -> Result<Account, DatabaseError> {
155 let joined = schema::accounts::table.inner_join(schema::account_codes::table.on(
157 schema::accounts::code_commitment.eq(schema::account_codes::code_commitment.nullable()),
158 ));
159
160 let (nonce, code_bytes): (Option<i64>, Vec<u8>) =
161 SelectDsl::select(joined, (schema::accounts::nonce, schema::account_codes::code))
162 .filter(schema::accounts::account_id.eq(account_id.to_bytes()))
163 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
164 .get_result(conn)
165 .optional()?
166 .ok_or(DatabaseError::AccountNotFoundInDb(account_id))?;
167
168 let nonce = raw_sql_to_nonce(nonce.ok_or_else(|| {
169 DatabaseError::DataCorrupted(format!("No nonce found for account {account_id}"))
170 })?);
171
172 let code = miden_node_persistence::decode::<AccountCode>(&code_bytes)?;
173
174 let storage = select_latest_account_storage(conn, account_id)?;
176
177 let vault_entries: Vec<(Vec<u8>, Option<Vec<u8>>)> = SelectDsl::select(
179 schema::account_vault_assets::table,
180 (schema::account_vault_assets::vault_key, schema::account_vault_assets::asset),
181 )
182 .filter(schema::account_vault_assets::account_id.eq(account_id.to_bytes()))
183 .filter(schema::account_vault_assets::valid_until.eq(VALID_FOREVER))
184 .load(conn)?;
185
186 let mut assets = Vec::new();
187 for (_key_bytes, maybe_asset_bytes) in vault_entries {
188 if let Some(asset_bytes) = maybe_asset_bytes {
189 let asset = miden_node_persistence::decode::<Asset>(&asset_bytes)?;
190 assets.push(asset);
191 }
192 }
193
194 let vault = AssetVault::new(&assets)?;
195
196 Ok(Account::new(account_id, vault, storage, code, nonce, None)?)
197}
198
199#[derive(Debug)]
201pub struct AccountCommitmentsPage {
202 pub commitments: Vec<(AccountId, Word)>,
204 pub next_cursor: Option<AccountId>,
206}
207
208pub(crate) fn select_account_commitments_paged(
229 conn: &mut SqliteConnection,
230 page_size: NonZeroUsize,
231 after_account_id: Option<AccountId>,
232) -> Result<AccountCommitmentsPage, DatabaseError> {
233 #[expect(clippy::cast_possible_wrap)]
235 let limit = (page_size.get() + 1) as i64;
236
237 let mut query = SelectDsl::select(
238 schema::accounts::table,
239 (schema::accounts::account_id, schema::accounts::account_commitment),
240 )
241 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
242 .order_by(schema::accounts::account_id.asc())
243 .limit(limit)
244 .into_boxed();
245
246 if let Some(cursor) = after_account_id {
247 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
248 }
249
250 let raw = query.load::<(Vec<u8>, Vec<u8>)>(conn)?;
251
252 let mut commitments = raw
253 .into_iter()
254 .map(|(ref account, ref commitment)| {
255 Ok((AccountId::read_from_bytes(account)?, Word::read_from_bytes(commitment)?))
256 })
257 .collect::<Result<Vec<_>, DatabaseError>>()?;
258
259 let next_cursor = if commitments.len() > page_size.get() {
261 commitments.pop(); commitments.last().map(|(id, _)| *id)
263 } else {
264 None
265 };
266
267 Ok(AccountCommitmentsPage { commitments, next_cursor })
268}
269
270#[derive(Debug)]
272pub struct PublicAccountIdsPage {
273 pub account_ids: Vec<AccountId>,
275 pub next_cursor: Option<AccountId>,
277}
278
279#[derive(Debug)]
281pub struct PublicAccountStateRoots {
282 pub account_id: AccountId,
283 pub vault_root: Word,
284 pub storage_header: AccountStorageHeader,
285}
286
287#[derive(Debug)]
289pub struct PublicAccountStateRootsPage {
290 pub accounts: Vec<PublicAccountStateRoots>,
292 pub next_cursor: Option<AccountId>,
294}
295
296pub(crate) fn select_public_account_ids_paged(
321 conn: &mut SqliteConnection,
322 page_size: NonZeroUsize,
323 after_account_id: Option<AccountId>,
324) -> Result<PublicAccountIdsPage, DatabaseError> {
325 #[expect(clippy::cast_possible_wrap)]
326 let limit = (page_size.get() + 1) as i64;
327
328 let mut query = SelectDsl::select(schema::accounts::table, schema::accounts::account_id)
329 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
330 .filter(schema::accounts::code_commitment.is_not_null())
331 .order_by(schema::accounts::account_id.asc())
332 .limit(limit)
333 .into_boxed();
334
335 if let Some(cursor) = after_account_id {
336 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
337 }
338
339 let raw = query.load::<Vec<u8>>(conn)?;
340
341 let mut account_ids: Vec<AccountId> = raw
342 .into_iter()
343 .map(|bytes| {
344 AccountId::read_from_bytes(&bytes).map_err(DatabaseError::DeserializationError)
345 })
346 .collect::<Result<_, _>>()?;
347
348 let next_cursor = if account_ids.len() > page_size.get() {
350 account_ids.pop(); account_ids.last().copied()
352 } else {
353 None
354 };
355
356 Ok(PublicAccountIdsPage { account_ids, next_cursor })
357}
358
359pub(crate) fn select_public_account_state_roots_paged(
386 conn: &mut SqliteConnection,
387 page_size: NonZeroUsize,
388 after_account_id: Option<AccountId>,
389) -> Result<PublicAccountStateRootsPage, DatabaseError> {
390 #[expect(clippy::cast_possible_wrap)]
391 let limit = (page_size.get() + 1) as i64;
392
393 let mut query = SelectDsl::select(
394 schema::accounts::table,
395 (
396 schema::accounts::account_id,
397 schema::accounts::vault_root,
398 schema::accounts::storage_header,
399 ),
400 )
401 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
402 .filter(schema::accounts::code_commitment.is_not_null())
403 .order_by(schema::accounts::account_id.asc())
404 .limit(limit)
405 .into_boxed();
406
407 if let Some(cursor) = after_account_id {
408 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
409 }
410
411 let raw = query.load::<(Vec<u8>, Option<Vec<u8>>, Option<Vec<u8>>)>(conn)?;
412
413 let mut accounts: Vec<PublicAccountStateRoots> = raw
414 .into_iter()
415 .map(|(account_id_bytes, vault_root_bytes, storage_header_bytes)| {
416 let account_id = AccountId::read_from_bytes(&account_id_bytes)
417 .map_err(DatabaseError::DeserializationError)?;
418 let vault_root_bytes = vault_root_bytes.ok_or_else(|| {
419 DatabaseError::DataCorrupted(format!(
420 "public account {account_id} is missing a vault root"
421 ))
422 })?;
423 let storage_header_bytes = storage_header_bytes.ok_or_else(|| {
424 DatabaseError::DataCorrupted(format!(
425 "public account {account_id} is missing a storage header"
426 ))
427 })?;
428
429 Ok::<_, DatabaseError>(PublicAccountStateRoots {
430 account_id,
431 vault_root: Word::read_from_bytes(&vault_root_bytes)?,
432 storage_header: miden_node_persistence::decode::<AccountStorageHeader>(
433 &storage_header_bytes,
434 )?,
435 })
436 })
437 .collect::<Result<_, _>>()?;
438
439 let next_cursor = if accounts.len() > page_size.get() {
441 accounts.pop();
442 accounts.last().map(|account| account.account_id)
443 } else {
444 None
445 };
446
447 Ok(PublicAccountStateRootsPage { accounts, next_cursor })
448}
449
450pub(crate) fn select_account_vault_assets(
478 conn: &mut SqliteConnection,
479 account_id: AccountId,
480 block_range: RangeInclusive<BlockNumber>,
481) -> Result<(BlockNumber, Vec<AccountVaultValue>), DatabaseError> {
482 use schema::account_vault_assets as t;
483 const ROW_OVERHEAD_BYTES: usize = 2 * size_of::<Word>() + size_of::<u32>(); const MAX_ROWS: usize = MAX_RESPONSE_PAYLOAD_BYTES / ROW_OVERHEAD_BYTES;
487
488 if !account_id.is_public() {
489 return Err(DatabaseError::AccountNotPublic(account_id));
490 }
491
492 if block_range.is_empty() {
493 return Err(DatabaseError::InvalidBlockRange {
494 from: *block_range.start(),
495 to: *block_range.end(),
496 });
497 }
498
499 let raw: Vec<(i64, Vec<u8>, Option<Vec<u8>>)> =
500 SelectDsl::select(t::table, (t::block_num, t::vault_key, t::asset))
501 .filter(
502 t::account_id
503 .eq(account_id.to_bytes())
504 .and(t::block_num.ge(block_range.start().to_raw_sql()))
505 .and(t::block_num.le(block_range.end().to_raw_sql())),
506 )
507 .order(t::block_num.asc())
508 .limit(i64::try_from(MAX_ROWS + 1).expect("should fit within i64"))
509 .load::<(i64, Vec<u8>, Option<Vec<u8>>)>(conn)?;
510
511 let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last()
514 && raw.len() > MAX_ROWS
515 {
516 let values = raw
517 .into_iter()
518 .take_while(|(bn, ..)| *bn != last_block_num)
519 .map(AccountVaultValue::from_raw_row)
520 .collect::<Result<Vec<_>, DatabaseError>>()?;
521
522 let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num);
523
524 (last_block_included, values)
525 } else {
526 (
527 *block_range.end(),
528 raw.into_iter().map(AccountVaultValue::from_raw_row).collect::<Result<_, _>>()?,
529 )
530 };
531
532 Ok((last_block_included, values))
533}
534
535#[cfg(test)]
556pub(crate) fn select_all_accounts(
557 conn: &mut SqliteConnection,
558) -> Result<Vec<AccountInfo>, DatabaseError> {
559 let raw = SelectDsl::select(schema::accounts::table, AccountSummaryRaw::as_select())
560 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
561 .order_by(schema::accounts::block_num.asc())
562 .load::<AccountSummaryRaw>(conn)?;
563
564 let summaries: Vec<AccountSummary> = vec_raw_try_into(raw)?;
565
566 let account_infos = summaries
568 .into_iter()
569 .map(|summary| {
570 let account_id = summary.account_id;
571 let details = select_full_account(conn, account_id).ok();
572 AccountInfo { summary, details }
573 })
574 .collect();
575
576 Ok(account_infos)
577}
578
579#[derive(Debug, Clone, PartialEq, Eq)]
580pub struct StorageMapValue {
581 pub block_num: BlockNumber,
582 pub slot_name: StorageSlotName,
583 pub key: StorageMapKey,
584 pub value: Word,
585}
586
587#[derive(Debug, Clone, PartialEq, Eq)]
588pub struct StorageMapValuesPage {
589 pub last_block_included: BlockNumber,
591 pub values: Vec<StorageMapValue>,
593}
594
595impl StorageMapValue {
596 pub fn from_raw_row(row: StorageMapValueRow) -> Result<Self, DatabaseError> {
597 let (block_num, slot_name, key, value) = row;
598 Ok(Self {
599 block_num: BlockNumber::from_raw_sql(block_num)?,
600 slot_name: StorageSlotName::from_raw_sql(slot_name)?,
601 key: StorageMapKey::read_from_bytes(&key)?,
602 value: Word::read_from_bytes(&value)?,
603 })
604 }
605}
606
607pub(crate) fn select_account_storage_map_values_paged(
649 conn: &mut SqliteConnection,
650 account_id: AccountId,
651 block_range: RangeInclusive<BlockNumber>,
652 limit: usize,
653) -> Result<StorageMapValuesPage, DatabaseError> {
654 use schema::account_storage_map_values as t;
655
656 if !account_id.is_public() {
657 return Err(DatabaseError::AccountNotPublic(account_id));
658 }
659
660 if block_range.is_empty() {
661 return Err(DatabaseError::InvalidBlockRange {
662 from: *block_range.start(),
663 to: *block_range.end(),
664 });
665 }
666
667 let raw: Vec<StorageMapValueRow> =
668 SelectDsl::select(t::table, (t::block_num, t::slot_name, t::key, t::value))
669 .filter(
670 t::account_id
671 .eq(account_id.to_bytes())
672 .and(t::block_num.ge(block_range.start().to_raw_sql()))
673 .and(t::block_num.le(block_range.end().to_raw_sql())),
674 )
675 .order(t::block_num.asc())
676 .limit(i64::try_from(limit + 1).expect("limit fits within i64"))
677 .load(conn)?;
678
679 let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last()
682 && raw.len() > limit
683 {
684 let values = raw
685 .into_iter()
686 .take_while(|(bn, ..)| *bn != last_block_num)
687 .map(StorageMapValue::from_raw_row)
688 .collect::<Result<Vec<_>, DatabaseError>>()?;
689
690 let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num);
691
692 (last_block_included, values)
693 } else {
694 (
695 *block_range.end(),
696 raw.into_iter()
697 .map(StorageMapValue::from_raw_row)
698 .collect::<Result<Vec<_>, _>>()?,
699 )
700 };
701
702 Ok(StorageMapValuesPage { last_block_included, values })
703}
704
705pub(crate) fn select_latest_account_storage(
711 conn: &mut SqliteConnection,
712 account_id: AccountId,
713) -> Result<AccountStorage, DatabaseError> {
714 let (storage_header, map_entries_by_slot) =
715 select_latest_account_storage_components(conn, account_id)?;
716 let slots = storage_header
718 .slots()
719 .map(|slot_header| {
720 let slot = match slot_header.slot_type() {
721 StorageSlotType::Value => {
722 StorageSlot::with_value(slot_header.name().clone(), slot_header.value())
724 },
725 StorageSlotType::Map => {
726 let entries =
728 map_entries_by_slot.get(slot_header.name()).cloned().unwrap_or_default();
729 let storage_map = StorageMap::with_entries(entries)?;
730 StorageSlot::with_map(slot_header.name().clone(), storage_map)
731 },
732 };
733 Ok(slot)
734 })
735 .collect::<Result<Vec<_>, DatabaseError>>()?;
736
737 Ok(AccountStorage::new(slots)?)
738}
739
740pub(crate) fn select_latest_account_storage_components(
742 conn: &mut SqliteConnection,
743 account_id: AccountId,
744) -> Result<StorageHeaderWithEntries, DatabaseError> {
745 let account_id_bytes = account_id.to_bytes();
746
747 let storage_blob: Option<Vec<u8>> =
749 SelectDsl::select(schema::accounts::table, schema::accounts::storage_header)
750 .filter(schema::accounts::account_id.eq(&account_id_bytes))
751 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
752 .first(conn)
753 .optional()?
754 .flatten();
755
756 let header = match storage_blob {
757 Some(blob) => miden_node_persistence::decode::<AccountStorageHeader>(&blob)?,
758 None => AccountStorageHeader::new(Vec::new())?,
759 };
760
761 let entries = select_latest_storage_map_entries_all(conn, &account_id)?;
762 Ok((header, entries))
763}
764
765fn select_latest_storage_map_entries_all(
767 conn: &mut SqliteConnection,
768 account_id: &AccountId,
769) -> Result<HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>, DatabaseError> {
770 use schema::account_storage_map_values as t;
771
772 let map_values: Vec<(String, Vec<u8>, Vec<u8>)> =
773 SelectDsl::select(t::table, (t::slot_name, t::key, t::value))
774 .filter(t::account_id.eq(&account_id.to_bytes()))
775 .filter(t::valid_until.eq(VALID_FOREVER))
776 .load(conn)?;
777
778 group_storage_map_entries(map_values)
779}
780
781fn group_storage_map_entries(
782 map_values: Vec<(String, Vec<u8>, Vec<u8>)>,
783) -> Result<HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>, DatabaseError> {
784 let mut map_entries_by_slot: HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>> =
785 HashMap::new();
786 for (slot_name_str, key_bytes, value_bytes) in map_values {
787 let slot_name: StorageSlotName = slot_name_str.parse().map_err(|_| {
788 DatabaseError::DataCorrupted(format!("Invalid slot name: {slot_name_str}"))
789 })?;
790 let key = StorageMapKey::read_from_bytes(&key_bytes)?;
791 let value = Word::read_from_bytes(&value_bytes)?;
792 map_entries_by_slot.entry(slot_name).or_default().insert(key, value);
793 }
794
795 Ok(map_entries_by_slot)
796}
797
798#[derive(Queryable, Selectable)]
802#[diesel(table_name = crate::db::schema::account_vault_assets)]
803#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
804pub struct AccountVaultUpdateRaw {
805 pub vault_key: Vec<u8>,
806 pub asset: Option<Vec<u8>>,
807 pub block_num: i64,
808}
809
810impl TryFrom<AccountVaultUpdateRaw> for AccountVaultValue {
811 type Error = DatabaseError;
812
813 fn try_from(raw: AccountVaultUpdateRaw) -> Result<Self, Self::Error> {
814 let vault_key = AssetId::try_from(Word::read_from_bytes(&raw.vault_key)?)?;
815 let asset = raw
816 .asset
817 .map(|bytes| miden_node_persistence::decode::<Asset>(&bytes))
818 .transpose()?;
819 let block_num = BlockNumber::from_raw_sql(raw.block_num)?;
820
821 Ok(AccountVaultValue { block_num, vault_key, asset })
822 }
823}
824
825#[derive(Debug, Clone, PartialEq, Eq, Selectable, Queryable, QueryableByName)]
826#[diesel(table_name = schema::accounts)]
827#[diesel(check_for_backend(Sqlite))]
828pub struct AccountSummaryRaw {
829 account_id: Vec<u8>, account_commitment: Vec<u8>, block_num: i64, }
833
834impl TryInto<AccountSummary> for AccountSummaryRaw {
835 type Error = DatabaseError;
836 fn try_into(self) -> Result<AccountSummary, Self::Error> {
837 let account_id = AccountId::read_from_bytes(&self.account_id[..])?;
838 let account_commitment = Word::read_from_bytes(&self.account_commitment[..])?;
839 let block_num = BlockNumber::from_raw_sql(self.block_num)?;
840
841 Ok(AccountSummary {
842 account_id,
843 account_commitment,
844 block_num,
845 })
846 }
847}