1use std::collections::{BTreeMap, HashMap, HashSet};
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 AsChangeset,
10 BoolExpressionMethods,
11 ExpressionMethods,
12 Insertable,
13 JoinOnDsl,
14 NullableExpressionMethods,
15 OptionalExtension,
16 QueryDsl,
17 RunQueryDsl,
18 Selectable,
19 SelectableHelper,
20 SqliteConnection,
21};
22use miden_node_proto::domain::account::{AccountInfo, AccountSummary, AccountVaultDetails};
23use miden_node_utils::limiter::{
24 MAX_RESPONSE_PAYLOAD_BYTES,
25 QueryParamAccountIdLimit,
26 QueryParamLimiter,
27};
28use miden_node_utils::tracing::miden_instrument;
29use miden_protocol::Word;
30use miden_protocol::account::{
31 Account,
32 AccountCode,
33 AccountId,
34 AccountPatch,
35 AccountStorage,
36 AccountStorageHeader,
37 AccountUpdateDetails,
38 StorageMap,
39 StorageMapKey,
40 StorageMapPatchEntries,
41 StorageSlot,
42 StorageSlotContent,
43 StorageSlotName,
44 StorageSlotType,
45};
46use miden_protocol::asset::{Asset, AssetId, AssetVault};
47use miden_protocol::block::{BlockAccountUpdate, BlockNumber};
48use miden_protocol::utils::serde::{Deserializable, Serializable};
49use miden_standards::account::auth::NetworkAccount;
50
51use crate::COMPONENT;
52use crate::db::models::conv::{SqlTypeConvert, nonce_to_raw_sql, raw_sql_to_nonce};
53#[cfg(test)]
54use crate::db::models::vec_raw_try_into;
55use crate::db::{AccountVaultValue, schema};
56use crate::errors::DatabaseError;
57
58mod at_block;
59pub(crate) use at_block::select_account_header_with_storage_header_at_block;
60
61mod delta;
62use delta::{
63 AccountStateForInsert,
64 LatestAccountStateRow,
65 PartialAccountState,
66 PrecomputedFullAccountState,
67 apply_storage_patch_with_roots,
68 select_latest_account_state,
69};
70
71#[cfg(test)]
72mod tests;
73
74type StorageMapValueRow = (i64, String, Vec<u8>, Vec<u8>);
75type StorageHeaderWithEntries =
76 (AccountStorageHeader, HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>);
77
78pub(crate) const VALID_FOREVER: i64 = i64::MAX;
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub(crate) enum NetworkAccountType {
93 None,
95 Network,
97}
98
99pub(crate) fn select_account_code_by_commitment(
114 conn: &mut SqliteConnection,
115 code_commitment: Word,
116) -> Result<Option<Vec<u8>>, DatabaseError> {
117 use schema::account_codes;
118
119 let code_commitment_bytes = code_commitment.to_bytes();
120
121 let result: Option<Vec<u8>> = SelectDsl::select(
122 account_codes::table.filter(account_codes::code_commitment.eq(&code_commitment_bytes)),
123 account_codes::code,
124 )
125 .first(conn)
126 .optional()?;
127
128 Ok(result)
129}
130
131pub(crate) fn select_account(
154 conn: &mut SqliteConnection,
155 account_id: AccountId,
156) -> Result<AccountInfo, DatabaseError> {
157 let raw = SelectDsl::select(schema::accounts::table, AccountSummaryRaw::as_select())
158 .filter(schema::accounts::account_id.eq(account_id.to_bytes()))
159 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
160 .get_result::<AccountSummaryRaw>(conn)
161 .optional()?
162 .ok_or(DatabaseError::AccountNotFoundInDb(account_id))?;
163
164 let summary: AccountSummary = raw.try_into()?;
165
166 let details = if account_id.is_public() {
169 Some(select_full_account(conn, account_id)?)
170 } else {
171 None
172 };
173
174 Ok(AccountInfo { summary, details })
175}
176
177pub(crate) fn select_full_account(
193 conn: &mut SqliteConnection,
194 account_id: AccountId,
195) -> Result<Account, DatabaseError> {
196 let joined = schema::accounts::table.inner_join(schema::account_codes::table.on(
198 schema::accounts::code_commitment.eq(schema::account_codes::code_commitment.nullable()),
199 ));
200
201 let (nonce, code_bytes): (Option<i64>, Vec<u8>) =
202 SelectDsl::select(joined, (schema::accounts::nonce, schema::account_codes::code))
203 .filter(schema::accounts::account_id.eq(account_id.to_bytes()))
204 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
205 .get_result(conn)
206 .optional()?
207 .ok_or(DatabaseError::AccountNotFoundInDb(account_id))?;
208
209 let nonce = raw_sql_to_nonce(nonce.ok_or_else(|| {
210 DatabaseError::DataCorrupted(format!("No nonce found for account {account_id}"))
211 })?);
212
213 let code = AccountCode::read_from_bytes(&code_bytes)?;
214
215 let storage = select_latest_account_storage(conn, account_id)?;
217
218 let vault_entries: Vec<(Vec<u8>, Option<Vec<u8>>)> = SelectDsl::select(
220 schema::account_vault_assets::table,
221 (schema::account_vault_assets::vault_key, schema::account_vault_assets::asset),
222 )
223 .filter(schema::account_vault_assets::account_id.eq(account_id.to_bytes()))
224 .filter(schema::account_vault_assets::valid_until.eq(VALID_FOREVER))
225 .load(conn)?;
226
227 let mut assets = Vec::new();
228 for (_key_bytes, maybe_asset_bytes) in vault_entries {
229 if let Some(asset_bytes) = maybe_asset_bytes {
230 let asset = Asset::read_from_bytes(&asset_bytes)?;
231 assets.push(asset);
232 }
233 }
234
235 let vault = AssetVault::new(&assets)?;
236
237 Ok(Account::new(account_id, vault, storage, code, nonce, None)?)
238}
239
240#[derive(Debug)]
242pub struct AccountCommitmentsPage {
243 pub commitments: Vec<(AccountId, Word)>,
245 pub next_cursor: Option<AccountId>,
247}
248
249pub(crate) fn select_account_commitments_paged(
270 conn: &mut SqliteConnection,
271 page_size: NonZeroUsize,
272 after_account_id: Option<AccountId>,
273) -> Result<AccountCommitmentsPage, DatabaseError> {
274 #[expect(clippy::cast_possible_wrap)]
276 let limit = (page_size.get() + 1) as i64;
277
278 let mut query = SelectDsl::select(
279 schema::accounts::table,
280 (schema::accounts::account_id, schema::accounts::account_commitment),
281 )
282 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
283 .order_by(schema::accounts::account_id.asc())
284 .limit(limit)
285 .into_boxed();
286
287 if let Some(cursor) = after_account_id {
288 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
289 }
290
291 let raw = query.load::<(Vec<u8>, Vec<u8>)>(conn)?;
292
293 let mut commitments = Result::<Vec<_>, DatabaseError>::from_iter(raw.into_iter().map(
294 |(ref account, ref commitment)| {
295 Ok((AccountId::read_from_bytes(account)?, Word::read_from_bytes(commitment)?))
296 },
297 ))?;
298
299 let next_cursor = if commitments.len() > page_size.get() {
301 commitments.pop(); commitments.last().map(|(id, _)| *id)
303 } else {
304 None
305 };
306
307 Ok(AccountCommitmentsPage { commitments, next_cursor })
308}
309
310#[derive(Debug)]
312pub struct PublicAccountIdsPage {
313 pub account_ids: Vec<AccountId>,
315 pub next_cursor: Option<AccountId>,
317}
318
319#[derive(Debug)]
321pub struct PublicAccountStateRoots {
322 pub account_id: AccountId,
323 pub vault_root: Word,
324 pub storage_header: AccountStorageHeader,
325}
326
327#[derive(Debug)]
329pub struct PublicAccountStateRootsPage {
330 pub accounts: Vec<PublicAccountStateRoots>,
332 pub next_cursor: Option<AccountId>,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
338pub(crate) struct PrecomputedPublicAccountState {
339 pub(crate) vault_root: Word,
340 pub(crate) storage_map_roots: BTreeMap<StorageSlotName, Word>,
341}
342
343pub(crate) type PrecomputedPublicAccountStates = BTreeMap<AccountId, PrecomputedPublicAccountState>;
344
345pub(crate) fn select_public_account_ids_paged(
370 conn: &mut SqliteConnection,
371 page_size: NonZeroUsize,
372 after_account_id: Option<AccountId>,
373) -> Result<PublicAccountIdsPage, DatabaseError> {
374 #[expect(clippy::cast_possible_wrap)]
375 let limit = (page_size.get() + 1) as i64;
376
377 let mut query = SelectDsl::select(schema::accounts::table, schema::accounts::account_id)
378 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
379 .filter(schema::accounts::code_commitment.is_not_null())
380 .order_by(schema::accounts::account_id.asc())
381 .limit(limit)
382 .into_boxed();
383
384 if let Some(cursor) = after_account_id {
385 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
386 }
387
388 let raw = query.load::<Vec<u8>>(conn)?;
389
390 let mut account_ids: Vec<AccountId> = Result::from_iter(raw.into_iter().map(|bytes| {
391 AccountId::read_from_bytes(&bytes).map_err(DatabaseError::DeserializationError)
392 }))?;
393
394 let next_cursor = if account_ids.len() > page_size.get() {
396 account_ids.pop(); account_ids.last().copied()
398 } else {
399 None
400 };
401
402 Ok(PublicAccountIdsPage { account_ids, next_cursor })
403}
404
405pub(crate) fn select_public_account_state_roots_paged(
432 conn: &mut SqliteConnection,
433 page_size: NonZeroUsize,
434 after_account_id: Option<AccountId>,
435) -> Result<PublicAccountStateRootsPage, DatabaseError> {
436 #[expect(clippy::cast_possible_wrap)]
437 let limit = (page_size.get() + 1) as i64;
438
439 let mut query = SelectDsl::select(
440 schema::accounts::table,
441 (
442 schema::accounts::account_id,
443 schema::accounts::vault_root,
444 schema::accounts::storage_header,
445 ),
446 )
447 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
448 .filter(schema::accounts::code_commitment.is_not_null())
449 .order_by(schema::accounts::account_id.asc())
450 .limit(limit)
451 .into_boxed();
452
453 if let Some(cursor) = after_account_id {
454 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
455 }
456
457 let raw = query.load::<(Vec<u8>, Option<Vec<u8>>, Option<Vec<u8>>)>(conn)?;
458
459 let mut accounts: Vec<PublicAccountStateRoots> = Result::from_iter(raw.into_iter().map(
460 |(account_id_bytes, vault_root_bytes, storage_header_bytes)| {
461 let account_id = AccountId::read_from_bytes(&account_id_bytes)
462 .map_err(DatabaseError::DeserializationError)?;
463 let vault_root_bytes = vault_root_bytes.ok_or_else(|| {
464 DatabaseError::DataCorrupted(format!(
465 "public account {account_id} is missing a vault root"
466 ))
467 })?;
468 let storage_header_bytes = storage_header_bytes.ok_or_else(|| {
469 DatabaseError::DataCorrupted(format!(
470 "public account {account_id} is missing a storage header"
471 ))
472 })?;
473
474 Ok::<_, DatabaseError>(PublicAccountStateRoots {
475 account_id,
476 vault_root: Word::read_from_bytes(&vault_root_bytes)?,
477 storage_header: AccountStorageHeader::read_from_bytes(&storage_header_bytes)?,
478 })
479 },
480 ))?;
481
482 let next_cursor = if accounts.len() > page_size.get() {
484 accounts.pop();
485 accounts.last().map(|account| account.account_id)
486 } else {
487 None
488 };
489
490 Ok(PublicAccountStateRootsPage { accounts, next_cursor })
491}
492
493pub(crate) fn select_account_vault_assets(
521 conn: &mut SqliteConnection,
522 account_id: AccountId,
523 block_range: RangeInclusive<BlockNumber>,
524) -> Result<(BlockNumber, Vec<AccountVaultValue>), DatabaseError> {
525 use schema::account_vault_assets as t;
526 const ROW_OVERHEAD_BYTES: usize = 2 * size_of::<Word>() + size_of::<u32>(); const MAX_ROWS: usize = MAX_RESPONSE_PAYLOAD_BYTES / ROW_OVERHEAD_BYTES;
530
531 if !account_id.is_public() {
532 return Err(DatabaseError::AccountNotPublic(account_id));
533 }
534
535 if block_range.is_empty() {
536 return Err(DatabaseError::InvalidBlockRange {
537 from: *block_range.start(),
538 to: *block_range.end(),
539 });
540 }
541
542 let raw: Vec<(i64, Vec<u8>, Option<Vec<u8>>)> =
543 SelectDsl::select(t::table, (t::block_num, t::vault_key, t::asset))
544 .filter(
545 t::account_id
546 .eq(account_id.to_bytes())
547 .and(t::block_num.ge(block_range.start().to_raw_sql()))
548 .and(t::block_num.le(block_range.end().to_raw_sql())),
549 )
550 .order(t::block_num.asc())
551 .limit(i64::try_from(MAX_ROWS + 1).expect("should fit within i64"))
552 .load::<(i64, Vec<u8>, Option<Vec<u8>>)>(conn)?;
553
554 let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last()
557 && raw.len() > MAX_ROWS
558 {
559 let values = raw
560 .into_iter()
561 .take_while(|(bn, ..)| *bn != last_block_num)
562 .map(AccountVaultValue::from_raw_row)
563 .collect::<Result<Vec<_>, DatabaseError>>()?;
564
565 let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num);
566
567 (last_block_included, values)
568 } else {
569 (
570 *block_range.end(),
571 raw.into_iter().map(AccountVaultValue::from_raw_row).collect::<Result<_, _>>()?,
572 )
573 };
574
575 Ok((last_block_included, values))
576}
577
578pub(crate) fn select_account_vault_at_block(
590 conn: &mut SqliteConnection,
591 account_id: AccountId,
592 block_num: BlockNumber,
593) -> Result<Vec<Asset>, DatabaseError> {
594 use diesel::sql_types::{BigInt, Binary};
595
596 let account_id_bytes = account_id.to_bytes();
597 let block_num_sql = block_num.to_raw_sql();
598 let limit_sql =
599 i64::try_from(AccountVaultDetails::MAX_RETURN_ENTRIES + 1).expect("should fit within i64");
600
601 let entries: Vec<Option<Vec<u8>>> = diesel::sql_query(
602 r"
603 SELECT asset FROM account_vault_assets
604 WHERE account_id = ?1 AND block_num <= ?2 AND valid_until > ?2
605 LIMIT ?3
606 ",
607 )
608 .bind::<Binary, _>(&account_id_bytes)
609 .bind::<BigInt, _>(block_num_sql)
610 .bind::<BigInt, _>(limit_sql)
611 .load::<AssetRow>(conn)?
612 .into_iter()
613 .map(|row| row.asset)
614 .collect();
615
616 let mut assets = Vec::new();
618 for asset_bytes in entries.into_iter().flatten() {
619 let asset = Asset::read_from_bytes(&asset_bytes)?;
620 assets.push(asset);
621 }
622
623 Ok(assets)
624}
625
626#[derive(QueryableByName)]
627struct AssetRow {
628 #[diesel(sql_type = diesel::sql_types::Nullable<diesel::sql_types::Binary>)]
629 asset: Option<Vec<u8>>,
630}
631
632#[cfg(test)]
653pub(crate) fn select_all_accounts(
654 conn: &mut SqliteConnection,
655) -> Result<Vec<AccountInfo>, DatabaseError> {
656 let raw = SelectDsl::select(schema::accounts::table, AccountSummaryRaw::as_select())
657 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
658 .order_by(schema::accounts::block_num.asc())
659 .load::<AccountSummaryRaw>(conn)?;
660
661 let summaries: Vec<AccountSummary> = vec_raw_try_into(raw)?;
662
663 let account_infos = summaries
665 .into_iter()
666 .map(|summary| {
667 let account_id = summary.account_id;
668 let details = select_full_account(conn, account_id).ok();
669 AccountInfo { summary, details }
670 })
671 .collect();
672
673 Ok(account_infos)
674}
675
676#[derive(Debug, Clone, PartialEq, Eq)]
677pub struct StorageMapValue {
678 pub block_num: BlockNumber,
679 pub slot_name: StorageSlotName,
680 pub key: StorageMapKey,
681 pub value: Word,
682}
683
684#[derive(Debug, Clone, PartialEq, Eq)]
685pub struct StorageMapValuesPage {
686 pub last_block_included: BlockNumber,
688 pub values: Vec<StorageMapValue>,
690}
691
692impl StorageMapValue {
693 pub fn from_raw_row(row: StorageMapValueRow) -> Result<Self, DatabaseError> {
694 let (block_num, slot_name, key, value) = row;
695 Ok(Self {
696 block_num: BlockNumber::from_raw_sql(block_num)?,
697 slot_name: StorageSlotName::from_raw_sql(slot_name)?,
698 key: StorageMapKey::read_from_bytes(&key)?,
699 value: Word::read_from_bytes(&value)?,
700 })
701 }
702}
703
704pub(crate) fn select_account_storage_map_values_paged(
746 conn: &mut SqliteConnection,
747 account_id: AccountId,
748 block_range: RangeInclusive<BlockNumber>,
749 limit: usize,
750) -> Result<StorageMapValuesPage, DatabaseError> {
751 use schema::account_storage_map_values as t;
752
753 if !account_id.is_public() {
754 return Err(DatabaseError::AccountNotPublic(account_id));
755 }
756
757 if block_range.is_empty() {
758 return Err(DatabaseError::InvalidBlockRange {
759 from: *block_range.start(),
760 to: *block_range.end(),
761 });
762 }
763
764 let raw: Vec<StorageMapValueRow> =
765 SelectDsl::select(t::table, (t::block_num, t::slot_name, t::key, t::value))
766 .filter(
767 t::account_id
768 .eq(account_id.to_bytes())
769 .and(t::block_num.ge(block_range.start().to_raw_sql()))
770 .and(t::block_num.le(block_range.end().to_raw_sql())),
771 )
772 .order(t::block_num.asc())
773 .limit(i64::try_from(limit + 1).expect("limit fits within i64"))
774 .load(conn)?;
775
776 let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last()
779 && raw.len() > limit
780 {
781 let values = raw
782 .into_iter()
783 .take_while(|(bn, ..)| *bn != last_block_num)
784 .map(StorageMapValue::from_raw_row)
785 .collect::<Result<Vec<_>, DatabaseError>>()?;
786
787 let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num);
788
789 (last_block_included, values)
790 } else {
791 (
792 *block_range.end(),
793 raw.into_iter()
794 .map(StorageMapValue::from_raw_row)
795 .collect::<Result<Vec<_>, _>>()?,
796 )
797 };
798
799 Ok(StorageMapValuesPage { last_block_included, values })
800}
801
802pub(crate) fn select_latest_account_storage(
808 conn: &mut SqliteConnection,
809 account_id: AccountId,
810) -> Result<AccountStorage, DatabaseError> {
811 let (storage_header, map_entries_by_slot) =
812 select_latest_account_storage_components(conn, account_id)?;
813 let slots =
815 Result::<Vec<_>, DatabaseError>::from_iter(storage_header.slots().map(|slot_header| {
816 let slot = match slot_header.slot_type() {
817 StorageSlotType::Value => {
818 StorageSlot::with_value(slot_header.name().clone(), slot_header.value())
820 },
821 StorageSlotType::Map => {
822 let entries =
824 map_entries_by_slot.get(slot_header.name()).cloned().unwrap_or_default();
825 let storage_map = StorageMap::with_entries(entries)?;
826 StorageSlot::with_map(slot_header.name().clone(), storage_map)
827 },
828 };
829 Ok(slot)
830 }))?;
831
832 Ok(AccountStorage::new(slots)?)
833}
834
835pub(crate) fn select_latest_account_storage_components(
837 conn: &mut SqliteConnection,
838 account_id: AccountId,
839) -> Result<StorageHeaderWithEntries, DatabaseError> {
840 let account_id_bytes = account_id.to_bytes();
841
842 let storage_blob: Option<Vec<u8>> =
844 SelectDsl::select(schema::accounts::table, schema::accounts::storage_header)
845 .filter(schema::accounts::account_id.eq(&account_id_bytes))
846 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
847 .first(conn)
848 .optional()?
849 .flatten();
850
851 let header = match storage_blob {
852 Some(blob) => AccountStorageHeader::read_from_bytes(&blob)?,
853 None => AccountStorageHeader::new(Vec::new())?,
854 };
855
856 let entries = select_latest_storage_map_entries_all(conn, &account_id)?;
857 Ok((header, entries))
858}
859
860fn select_latest_storage_map_entries_all(
862 conn: &mut SqliteConnection,
863 account_id: &AccountId,
864) -> Result<HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>, DatabaseError> {
865 use schema::account_storage_map_values as t;
866
867 let map_values: Vec<(String, Vec<u8>, Vec<u8>)> =
868 SelectDsl::select(t::table, (t::slot_name, t::key, t::value))
869 .filter(t::account_id.eq(&account_id.to_bytes()))
870 .filter(t::valid_until.eq(VALID_FOREVER))
871 .load(conn)?;
872
873 group_storage_map_entries(map_values)
874}
875
876fn group_storage_map_entries(
877 map_values: Vec<(String, Vec<u8>, Vec<u8>)>,
878) -> Result<HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>, DatabaseError> {
879 let mut map_entries_by_slot: HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>> =
880 HashMap::new();
881 for (slot_name_str, key_bytes, value_bytes) in map_values {
882 let slot_name: StorageSlotName = slot_name_str.parse().map_err(|_| {
883 DatabaseError::DataCorrupted(format!("Invalid slot name: {slot_name_str}"))
884 })?;
885 let key = StorageMapKey::read_from_bytes(&key_bytes)?;
886 let value = Word::read_from_bytes(&value_bytes)?;
887 map_entries_by_slot.entry(slot_name).or_default().insert(key, value);
888 }
889
890 Ok(map_entries_by_slot)
891}
892
893#[derive(Queryable, Selectable)]
897#[diesel(table_name = crate::db::schema::account_vault_assets)]
898#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
899pub struct AccountVaultUpdateRaw {
900 pub vault_key: Vec<u8>,
901 pub asset: Option<Vec<u8>>,
902 pub block_num: i64,
903}
904
905impl TryFrom<AccountVaultUpdateRaw> for AccountVaultValue {
906 type Error = DatabaseError;
907
908 fn try_from(raw: AccountVaultUpdateRaw) -> Result<Self, Self::Error> {
909 let vault_key = AssetId::try_from(Word::read_from_bytes(&raw.vault_key)?)?;
910 let asset = raw.asset.map(|bytes| Asset::read_from_bytes(&bytes)).transpose()?;
911 let block_num = BlockNumber::from_raw_sql(raw.block_num)?;
912
913 Ok(AccountVaultValue { block_num, vault_key, asset })
914 }
915}
916
917#[derive(Debug, Clone, PartialEq, Eq, Selectable, Queryable, QueryableByName)]
918#[diesel(table_name = schema::accounts)]
919#[diesel(check_for_backend(Sqlite))]
920pub struct AccountSummaryRaw {
921 account_id: Vec<u8>, account_commitment: Vec<u8>, block_num: i64, }
925
926impl TryInto<AccountSummary> for AccountSummaryRaw {
927 type Error = DatabaseError;
928 fn try_into(self) -> Result<AccountSummary, Self::Error> {
929 let account_id = AccountId::read_from_bytes(&self.account_id[..])?;
930 let account_commitment = Word::read_from_bytes(&self.account_commitment[..])?;
931 let block_num = BlockNumber::from_raw_sql(self.block_num)?;
932
933 Ok(AccountSummary {
934 account_id,
935 account_commitment,
936 block_num,
937 })
938 }
939}
940
941pub(crate) fn insert_account_vault_asset(
950 conn: &mut SqliteConnection,
951 account_id: AccountId,
952 block_num: BlockNumber,
953 vault_key: AssetId,
954 asset: Option<Asset>,
955) -> Result<usize, DatabaseError> {
956 let record = AccountAssetRowInsert::new(&account_id, &vault_key, block_num, asset);
957
958 diesel::Connection::transaction(conn, |conn| {
959 let vault_key: Word = vault_key.into();
961 let vault_key_bytes = vault_key.to_bytes();
962 let account_id_bytes = account_id.to_bytes();
963 let update_count = diesel::update(schema::account_vault_assets::table)
964 .filter(
965 schema::account_vault_assets::account_id
966 .eq(account_id_bytes)
967 .and(schema::account_vault_assets::vault_key.eq(vault_key_bytes))
968 .and(schema::account_vault_assets::valid_until.eq(VALID_FOREVER)),
969 )
970 .set(schema::account_vault_assets::valid_until.eq(block_num.to_raw_sql()))
971 .execute(conn)?;
972
973 let insert_count = diesel::insert_into(schema::account_vault_assets::table)
975 .values(record)
976 .execute(conn)?;
977
978 Ok(update_count + insert_count)
979 })
980}
981
982pub(crate) fn insert_account_storage_map_value(
995 conn: &mut SqliteConnection,
996 account_id: AccountId,
997 block_num: BlockNumber,
998 slot_name: StorageSlotName,
999 key: StorageMapKey,
1000 value: Word,
1001) -> Result<usize, DatabaseError> {
1002 insert_account_storage_map_value_inner(conn, account_id, block_num, slot_name, key, value, true)
1003}
1004
1005fn insert_account_storage_map_value_inner(
1018 conn: &mut SqliteConnection,
1019 account_id: AccountId,
1020 block_num: BlockNumber,
1021 slot_name: StorageSlotName,
1022 key: StorageMapKey,
1023 value: Word,
1024 invalidate_previous: bool,
1025) -> Result<usize, DatabaseError> {
1026 let account_id = account_id.to_bytes();
1027 let key = key.to_bytes();
1028 let value = value.to_bytes();
1029 let slot_name = slot_name.to_raw_sql();
1030 let block_num = block_num.to_raw_sql();
1031
1032 let update_count = if invalidate_previous {
1033 diesel::update(schema::account_storage_map_values::table)
1034 .filter(
1035 schema::account_storage_map_values::account_id
1036 .eq(&account_id)
1037 .and(schema::account_storage_map_values::slot_name.eq(&slot_name))
1038 .and(schema::account_storage_map_values::key.eq(&key))
1039 .and(schema::account_storage_map_values::valid_until.eq(VALID_FOREVER)),
1040 )
1041 .set(schema::account_storage_map_values::valid_until.eq(block_num))
1042 .execute(conn)?
1043 } else {
1044 0
1045 };
1046
1047 let record = AccountStorageMapRowInsert {
1048 account_id,
1049 key,
1050 value,
1051 slot_name,
1052 block_num,
1053 valid_until: VALID_FOREVER,
1054 };
1055 let insert_count = diesel::insert_into(schema::account_storage_map_values::table)
1056 .values(record)
1057 .execute(conn)?;
1058
1059 Ok(update_count + insert_count)
1060}
1061
1062type PendingStorageInserts = Vec<(AccountId, StorageSlotName, StorageMapKey, Word)>;
1063type PendingAssetInserts = Vec<(AccountId, AssetId, Option<Asset>)>;
1064
1065fn prepare_full_account_update(
1066 update: &BlockAccountUpdate,
1067 account: Account,
1068) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
1069 let account_id = account.id();
1070
1071 if account.to_commitment() != update.final_state_commitment() {
1073 return Err(DatabaseError::AccountCommitmentsMismatch {
1074 calculated: account.to_commitment(),
1075 expected: update.final_state_commitment(),
1076 });
1077 }
1078
1079 let mut storage = Vec::new();
1081 for slot in account.storage().slots() {
1082 if let StorageSlotContent::Map(storage_map) = slot.content() {
1083 for (key, value) in storage_map.entries() {
1084 storage.push((account_id, slot.name().clone(), *key, *value));
1085 }
1086 }
1087 }
1088
1089 let mut assets = Vec::new();
1091 for asset in account.vault().assets() {
1092 let should_insert = match asset {
1094 Asset::Fungible(fungible) => fungible.amount().as_u64() > 0,
1095 Asset::NonFungible(_) => true,
1096 };
1097 if should_insert {
1098 assets.push((account_id, asset.id(), Some(asset)));
1099 }
1100 }
1101
1102 Ok((AccountStateForInsert::FullAccount(account), storage, assets))
1103}
1104
1105fn prepare_precomputed_full_account_update(
1118 update: &BlockAccountUpdate,
1119 patch: &AccountPatch,
1120 precomputed: &PrecomputedPublicAccountState,
1121) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
1122 let account_id = patch.id();
1123 let code = patch.code().cloned().ok_or_else(|| {
1124 DatabaseError::DataCorrupted(format!(
1125 "full-state patch for account {account_id} is missing account code"
1126 ))
1127 })?;
1128 let nonce = patch.final_nonce().ok_or_else(|| {
1129 DatabaseError::DataCorrupted(format!(
1130 "full-state patch for account {account_id} is missing final nonce"
1131 ))
1132 })?;
1133
1134 let storage_header = apply_storage_patch_with_roots(
1135 &AccountStorageHeader::new(Vec::new())?,
1136 patch.storage(),
1137 &precomputed.storage_map_roots,
1138 )?;
1139 let account_header = miden_protocol::account::AccountHeader::new(
1140 account_id,
1141 nonce,
1142 precomputed.vault_root,
1143 storage_header.to_commitment(),
1144 code.commitment(),
1145 );
1146 if account_header.to_commitment() != update.final_state_commitment() {
1147 return Err(DatabaseError::AccountCommitmentsMismatch {
1148 calculated: account_header.to_commitment(),
1149 expected: update.final_state_commitment(),
1150 });
1151 }
1152
1153 let storage = patch
1154 .storage()
1155 .maps()
1156 .flat_map(|(slot_name, map_patch)| {
1157 map_patch.entries().into_iter().flat_map(move |entries| {
1158 entries
1159 .as_map()
1160 .iter()
1161 .filter(|(_key, value)| **value != Word::empty())
1162 .map(move |(key, value)| (account_id, slot_name.clone(), *key, *value))
1163 })
1164 })
1165 .collect();
1166 let assets = patch
1167 .vault()
1168 .iter()
1169 .filter(|(_asset_id, value)| **value != Word::empty())
1170 .map(|(asset_id, value)| {
1171 Asset::from_id_and_value(*asset_id, *value)
1172 .map(|asset| (account_id, *asset_id, Some(asset)))
1173 })
1174 .collect::<Result<Vec<_>, _>>()?;
1175
1176 let is_network_account = NetworkAccount::new(Account::try_from(patch)?).is_ok();
1179 let state = PrecomputedFullAccountState {
1180 nonce,
1181 code,
1182 storage_header,
1183 vault_root: precomputed.vault_root,
1184 is_network_account,
1185 };
1186
1187 Ok((AccountStateForInsert::PrecomputedFullState(state), storage, assets))
1188}
1189
1190fn prepare_partial_account_update(
1202 update: &BlockAccountUpdate,
1203 account_id: AccountId,
1204 patch: &AccountPatch,
1205 precomputed: &PrecomputedPublicAccountState,
1206 existing: &LatestAccountStateRow,
1207) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
1208 let state_headers = existing.state_headers(account_id)?;
1211
1212 let mut assets = Vec::new();
1215 for (vault_key, value) in patch.vault().iter() {
1216 let update_or_remove = if *value == Word::empty() {
1217 None
1218 } else {
1219 Some(Asset::from_id_and_value(*vault_key, *value)?)
1220 };
1221 assets.push((account_id, *vault_key, update_or_remove));
1222 }
1223
1224 let mut storage = Vec::new();
1227 for (slot_name, map_patch) in patch.storage().maps() {
1228 for (key, value) in map_patch.entries().into_iter().flat_map(StorageMapPatchEntries::as_map)
1229 {
1230 storage.push((account_id, slot_name.clone(), *key, *value));
1231 }
1232 }
1233
1234 let new_storage_header = apply_storage_patch_with_roots(
1236 &state_headers.storage_header,
1237 patch.storage(),
1238 &precomputed.storage_map_roots,
1239 )?;
1240
1241 let new_vault_root = precomputed.vault_root;
1242
1243 let new_nonce = patch.final_nonce().unwrap_or(state_headers.nonce);
1245
1246 let account_state = PartialAccountState {
1248 nonce: new_nonce,
1249 code_commitment: state_headers.code_commitment,
1250 storage_header: new_storage_header,
1251 vault_root: new_vault_root,
1252 };
1253
1254 let account_header = miden_protocol::account::AccountHeader::new(
1255 account_id,
1256 account_state.nonce,
1257 account_state.vault_root,
1258 account_state.storage_header.to_commitment(),
1259 account_state.code_commitment,
1260 );
1261
1262 if account_header.to_commitment() != update.final_state_commitment() {
1263 return Err(DatabaseError::AccountCommitmentsMismatch {
1264 calculated: account_header.to_commitment(),
1265 expected: update.final_state_commitment(),
1266 });
1267 }
1268
1269 Ok((AccountStateForInsert::PartialState(account_state), storage, assets))
1270}
1271
1272pub(crate) fn select_network_accounts_subset(
1276 conn: &mut SqliteConnection,
1277 account_ids: &[AccountId],
1278) -> Result<HashSet<AccountId>, DatabaseError> {
1279 QueryParamAccountIdLimit::check(account_ids.len())?;
1280 let id_bytes: Vec<Vec<u8>> =
1281 account_ids.iter().map(miden_crypto::utils::Serializable::to_bytes).collect();
1282
1283 let rows: Vec<Vec<u8>> =
1284 SelectDsl::select(schema::accounts::table, schema::accounts::account_id)
1285 .filter(
1286 schema::accounts::account_id
1287 .eq_any(&id_bytes)
1288 .and(
1289 schema::accounts::network_account_type
1290 .eq(NetworkAccountType::Network.to_raw_sql()),
1291 )
1292 .and(schema::accounts::valid_until.eq(VALID_FOREVER)),
1293 )
1294 .load::<Vec<u8>>(conn)
1295 .map_err(DatabaseError::Diesel)?;
1296
1297 rows.into_iter()
1298 .map(|bytes| {
1299 AccountId::read_from_bytes(&bytes).map_err(DatabaseError::DeserializationError)
1300 })
1301 .collect()
1302}
1303
1304#[miden_instrument(
1306 target = COMPONENT,
1307 err,
1308)]
1309pub(crate) fn upsert_accounts(
1310 conn: &mut SqliteConnection,
1311 accounts: &[BlockAccountUpdate],
1312 block_num: BlockNumber,
1313 precomputed_public_states: &PrecomputedPublicAccountStates,
1314) -> Result<usize, DatabaseError> {
1315 let mut count = 0;
1316 for update in accounts {
1317 let account_id = update.account_id();
1318 let account_id_bytes = account_id.to_bytes();
1319
1320 let existing = select_latest_account_state(conn, account_id)?;
1323 let account_is_new = existing.is_none();
1324
1325 let created_at_block = match &existing {
1326 Some(row) => row.created_at_block()?,
1327 None => block_num,
1328 };
1329
1330 let (account_state, pending_storage_inserts, pending_asset_inserts) = match update.details()
1335 {
1336 AccountUpdateDetails::Private => (AccountStateForInsert::Private, vec![], vec![]),
1337
1338 AccountUpdateDetails::Public(patch) if patch.is_full_state() => {
1340 if block_num == BlockNumber::GENESIS {
1341 let account = Account::try_from(patch)
1342 .expect("Patch to full account always works for full state patches");
1343 debug_assert_eq!(account_id, account.id());
1344 prepare_full_account_update(update, account)?
1345 } else {
1346 let precomputed =
1347 precomputed_public_states.get(&account_id).ok_or_else(|| {
1348 DatabaseError::DataCorrupted(format!(
1349 "missing precomputed public account state for account {account_id}"
1350 ))
1351 })?;
1352 prepare_precomputed_full_account_update(update, patch, precomputed)?
1353 }
1354 },
1355
1356 AccountUpdateDetails::Public(patch) => {
1358 let precomputed = precomputed_public_states.get(&account_id).ok_or_else(|| {
1359 DatabaseError::DataCorrupted(format!(
1360 "missing precomputed public account state for account {account_id}"
1361 ))
1362 })?;
1363 let existing =
1364 existing.as_ref().ok_or(DatabaseError::AccountNotFoundInDb(account_id))?;
1365 prepare_partial_account_update(update, account_id, patch, precomputed, existing)?
1366 },
1367 };
1368
1369 let network_account_type = match &existing {
1372 Some(row) => row.network_account_type()?,
1373 None => match &account_state {
1374 AccountStateForInsert::FullAccount(account)
1375 if NetworkAccount::new(account.clone()).is_ok() =>
1376 {
1377 NetworkAccountType::Network
1378 },
1379 AccountStateForInsert::PrecomputedFullState(state) if state.is_network_account => {
1380 NetworkAccountType::Network
1381 },
1382 _ => NetworkAccountType::None,
1383 },
1384 };
1385
1386 if let AccountStateForInsert::FullAccount(ref account) = account_state {
1388 let code = account.code();
1389 let code_value = AccountCodeRowInsert {
1390 code_commitment: code.commitment().to_bytes(),
1391 code: code.to_bytes(),
1392 };
1393 diesel::insert_into(schema::account_codes::table)
1394 .values(&code_value)
1395 .on_conflict(schema::account_codes::code_commitment)
1396 .do_nothing()
1397 .execute(conn)?;
1398 }
1399 if let AccountStateForInsert::PrecomputedFullState(ref state) = account_state {
1400 let code_value = AccountCodeRowInsert {
1401 code_commitment: state.code.commitment().to_bytes(),
1402 code: state.code.to_bytes(),
1403 };
1404 diesel::insert_into(schema::account_codes::table)
1405 .values(&code_value)
1406 .on_conflict(schema::account_codes::code_commitment)
1407 .do_nothing()
1408 .execute(conn)?;
1409 }
1410
1411 diesel::update(schema::accounts::table)
1413 .filter(
1414 schema::accounts::account_id
1415 .eq(&account_id_bytes)
1416 .and(schema::accounts::valid_until.eq(VALID_FOREVER)),
1417 )
1418 .set(schema::accounts::valid_until.eq(block_num.to_raw_sql()))
1419 .execute(conn)?;
1420
1421 let account_value = match &account_state {
1422 AccountStateForInsert::Private => AccountRowInsert::new_private(
1423 account_id,
1424 network_account_type,
1425 update.final_state_commitment(),
1426 block_num,
1427 created_at_block,
1428 ),
1429 AccountStateForInsert::FullAccount(account) => AccountRowInsert::new_from_account(
1430 account_id,
1431 network_account_type,
1432 update.final_state_commitment(),
1433 block_num,
1434 created_at_block,
1435 account,
1436 ),
1437 AccountStateForInsert::PrecomputedFullState(state) => {
1438 AccountRowInsert::new_from_precomputed_full_state(
1439 account_id,
1440 network_account_type,
1441 update.final_state_commitment(),
1442 block_num,
1443 created_at_block,
1444 state,
1445 )
1446 },
1447 AccountStateForInsert::PartialState(state) => AccountRowInsert::new_from_partial(
1448 account_id,
1449 network_account_type,
1450 update.final_state_commitment(),
1451 block_num,
1452 created_at_block,
1453 state,
1454 ),
1455 };
1456
1457 diesel::insert_into(schema::accounts::table)
1458 .values(&account_value)
1459 .on_conflict((schema::accounts::account_id, schema::accounts::block_num))
1460 .do_update()
1461 .set(&account_value)
1462 .execute(conn)?;
1463
1464 for (acc_id, slot_name, key, value) in pending_storage_inserts {
1466 if account_is_new {
1467 insert_account_storage_map_value_inner(
1468 conn, acc_id, block_num, slot_name, key, value, false,
1469 )?;
1470 } else {
1471 insert_account_storage_map_value(conn, acc_id, block_num, slot_name, key, value)?;
1472 }
1473 }
1474
1475 for (acc_id, vault_key, update) in pending_asset_inserts {
1476 insert_account_vault_asset(conn, acc_id, block_num, vault_key, update)?;
1477 }
1478
1479 count += 1;
1480 }
1481
1482 Ok(count)
1483}
1484
1485#[derive(Insertable, Debug, Clone)]
1486#[diesel(table_name = schema::account_codes)]
1487pub(crate) struct AccountCodeRowInsert {
1488 pub(crate) code_commitment: Vec<u8>,
1489 pub(crate) code: Vec<u8>,
1490}
1491
1492#[derive(Insertable, AsChangeset, Debug, Clone)]
1493#[diesel(table_name = schema::accounts)]
1494pub(crate) struct AccountRowInsert {
1495 pub(crate) account_id: Vec<u8>,
1496 pub(crate) network_account_type: i32,
1497 pub(crate) block_num: i64,
1498 pub(crate) account_commitment: Vec<u8>,
1499 pub(crate) code_commitment: Option<Vec<u8>>,
1500 pub(crate) nonce: Option<i64>,
1501 pub(crate) storage_header: Option<Vec<u8>>,
1502 pub(crate) vault_root: Option<Vec<u8>>,
1503 pub(crate) created_at_block: i64,
1504 pub(crate) valid_until: i64,
1505}
1506
1507impl AccountRowInsert {
1508 pub(crate) fn new_private(
1510 account_id: AccountId,
1511 network_account_type: NetworkAccountType,
1512 account_commitment: Word,
1513 block_num: BlockNumber,
1514 created_at_block: BlockNumber,
1515 ) -> Self {
1516 Self {
1517 account_id: account_id.to_bytes(),
1518 network_account_type: network_account_type.to_raw_sql(),
1519 account_commitment: account_commitment.to_bytes(),
1520 block_num: block_num.to_raw_sql(),
1521 nonce: None,
1522 code_commitment: None,
1523 storage_header: None,
1524 vault_root: None,
1525 created_at_block: created_at_block.to_raw_sql(),
1526 valid_until: VALID_FOREVER,
1527 }
1528 }
1529
1530 fn new_from_account(
1532 account_id: AccountId,
1533 network_account_type: NetworkAccountType,
1534 account_commitment: Word,
1535 block_num: BlockNumber,
1536 created_at_block: BlockNumber,
1537 account: &Account,
1538 ) -> Self {
1539 Self {
1540 account_id: account_id.to_bytes(),
1541 network_account_type: network_account_type.to_raw_sql(),
1542 account_commitment: account_commitment.to_bytes(),
1543 block_num: block_num.to_raw_sql(),
1544 nonce: Some(nonce_to_raw_sql(account.nonce())),
1545 code_commitment: Some(account.code().commitment().to_bytes()),
1546 storage_header: Some(account.storage().to_header().to_bytes()),
1547 vault_root: Some(account.vault().root().to_bytes()),
1548 created_at_block: created_at_block.to_raw_sql(),
1549 valid_until: VALID_FOREVER,
1550 }
1551 }
1552
1553 fn new_from_precomputed_full_state(
1554 account_id: AccountId,
1555 network_account_type: NetworkAccountType,
1556 account_commitment: Word,
1557 block_num: BlockNumber,
1558 created_at_block: BlockNumber,
1559 state: &PrecomputedFullAccountState,
1560 ) -> Self {
1561 Self {
1562 account_id: account_id.to_bytes(),
1563 network_account_type: network_account_type.to_raw_sql(),
1564 block_num: block_num.to_raw_sql(),
1565 account_commitment: account_commitment.to_bytes(),
1566 code_commitment: Some(state.code.commitment().to_bytes()),
1567 nonce: Some(nonce_to_raw_sql(state.nonce)),
1568 storage_header: Some(state.storage_header.to_bytes()),
1569 vault_root: Some(state.vault_root.to_bytes()),
1570 created_at_block: created_at_block.to_raw_sql(),
1571 valid_until: VALID_FOREVER,
1572 }
1573 }
1574
1575 fn new_from_partial(
1577 account_id: AccountId,
1578 network_account_type: NetworkAccountType,
1579 account_commitment: Word,
1580 block_num: BlockNumber,
1581 created_at_block: BlockNumber,
1582 state: &PartialAccountState,
1583 ) -> Self {
1584 Self {
1585 account_id: account_id.to_bytes(),
1586 network_account_type: network_account_type.to_raw_sql(),
1587 account_commitment: account_commitment.to_bytes(),
1588 block_num: block_num.to_raw_sql(),
1589 nonce: Some(nonce_to_raw_sql(state.nonce)),
1590 code_commitment: Some(state.code_commitment.to_bytes()),
1591 storage_header: Some(state.storage_header.to_bytes()),
1592 vault_root: Some(state.vault_root.to_bytes()),
1593 created_at_block: created_at_block.to_raw_sql(),
1594 valid_until: VALID_FOREVER,
1595 }
1596 }
1597}
1598
1599#[derive(Insertable, AsChangeset, Debug, Clone)]
1600#[diesel(table_name = schema::account_vault_assets)]
1601pub(crate) struct AccountAssetRowInsert {
1602 pub(crate) account_id: Vec<u8>,
1603 pub(crate) block_num: i64,
1604 pub(crate) vault_key: Vec<u8>,
1605 pub(crate) asset: Option<Vec<u8>>,
1606 pub(crate) valid_until: i64,
1607}
1608
1609impl AccountAssetRowInsert {
1610 pub(crate) fn new(
1611 account_id: &AccountId,
1612 vault_key: &AssetId,
1613 block_num: BlockNumber,
1614 asset: Option<Asset>,
1615 ) -> Self {
1616 let account_id = account_id.to_bytes();
1617 let vault_key: Word = (*vault_key).into();
1618 let vault_key = vault_key.to_bytes();
1619 let block_num = block_num.to_raw_sql();
1620 let asset = asset.map(|asset| asset.to_bytes());
1621 Self {
1622 account_id,
1623 block_num,
1624 vault_key,
1625 asset,
1626 valid_until: VALID_FOREVER,
1627 }
1628 }
1629}
1630
1631#[derive(Insertable, AsChangeset, Debug, Clone)]
1632#[diesel(table_name = schema::account_storage_map_values)]
1633pub(crate) struct AccountStorageMapRowInsert {
1634 pub(crate) account_id: Vec<u8>,
1635 pub(crate) block_num: i64,
1636 pub(crate) slot_name: String,
1637 pub(crate) key: Vec<u8>,
1638 pub(crate) value: Vec<u8>,
1639 pub(crate) valid_until: i64,
1640}
1641
1642pub const HISTORICAL_BLOCK_RETENTION: u32 = 50;
1650
1651#[miden_instrument(
1664 target = COMPONENT,
1665 err,
1666 fields(
1667 cutoff_block,
1668 ),
1669)]
1670pub(crate) fn prune_history(
1671 conn: &mut SqliteConnection,
1672 prune_tip: BlockNumber,
1673) -> Result<(usize, usize, usize), DatabaseError> {
1674 let cutoff_block = i64::from(prune_tip.as_u32().saturating_sub(HISTORICAL_BLOCK_RETENTION));
1675 tracing::Span::current().record("cutoff_block", cutoff_block);
1676 let vault_deleted = prune_account_vault_assets(conn, cutoff_block)?;
1677 let storage_deleted = prune_account_storage_map_values(conn, cutoff_block)?;
1678 let codes_deleted = prune_account_codes(conn, cutoff_block)?;
1679
1680 Ok((vault_deleted, storage_deleted, codes_deleted))
1681}
1682
1683#[miden_instrument(
1684 target = COMPONENT,
1685 err,
1686 fields(
1687 cutoff_block,
1688 ),
1689)]
1690fn prune_account_vault_assets(
1691 conn: &mut SqliteConnection,
1692 cutoff_block: i64,
1693) -> Result<usize, DatabaseError> {
1694 use diesel::sql_types::BigInt;
1695
1696 diesel::sql_query(format!(
1699 "DELETE FROM account_vault_assets \
1700 WHERE valid_until != {VALID_FOREVER} \
1701 AND valid_until <= ?1"
1702 ))
1703 .bind::<BigInt, _>(cutoff_block)
1704 .execute(conn)
1705 .map_err(DatabaseError::Diesel)
1706}
1707
1708#[miden_instrument(
1709 target = COMPONENT,
1710 err,
1711 fields(
1712 cutoff_block,
1713 ),
1714)]
1715fn prune_account_storage_map_values(
1716 conn: &mut SqliteConnection,
1717 cutoff_block: i64,
1718) -> Result<usize, DatabaseError> {
1719 use diesel::sql_types::BigInt;
1720
1721 diesel::sql_query(format!(
1724 "DELETE FROM account_storage_map_values \
1725 WHERE valid_until != {VALID_FOREVER} \
1726 AND valid_until <= ?1"
1727 ))
1728 .bind::<BigInt, _>(cutoff_block)
1729 .execute(conn)
1730 .map_err(DatabaseError::Diesel)
1731}
1732
1733#[miden_instrument(
1761 target = COMPONENT,
1762 err,
1763 fields(
1764 cutoff_block,
1765 ),
1766)]
1767fn prune_account_codes(
1768 conn: &mut SqliteConnection,
1769 cutoff_block: i64,
1770) -> Result<usize, DatabaseError> {
1771 use diesel::sql_types::BigInt;
1772
1773 let prev_cutoff: Option<i64> =
1774 SelectDsl::select(schema::prune_progress::table, schema::prune_progress::codes_cutoff)
1775 .first(conn)
1776 .optional()
1777 .map_err(DatabaseError::Diesel)?;
1778
1779 let deleted = match prev_cutoff {
1780 Some(prev_cutoff) if prev_cutoff >= cutoff_block => return Ok(0),
1787 Some(prev_cutoff) => diesel::sql_query(
1788 "DELETE FROM account_codes \
1789 WHERE code_commitment IN ( \
1790 SELECT DISTINCT code_commitment \
1791 FROM accounts INDEXED BY idx_accounts_code_validity \
1792 WHERE code_commitment IS NOT NULL \
1793 AND valid_until > ?1 \
1794 AND valid_until <= ?2 \
1795 ) \
1796 AND NOT EXISTS ( \
1797 SELECT 1 \
1798 FROM accounts INDEXED BY idx_accounts_code_probe \
1799 WHERE accounts.code_commitment = account_codes.code_commitment \
1800 AND accounts.valid_until > ?2 \
1801 )",
1802 )
1803 .bind::<BigInt, _>(prev_cutoff)
1804 .bind::<BigInt, _>(cutoff_block)
1805 .execute(conn)
1806 .map_err(DatabaseError::Diesel)?,
1807 None => diesel::sql_query(
1811 "DELETE FROM account_codes \
1812 WHERE code_commitment NOT IN ( \
1813 SELECT DISTINCT code_commitment \
1814 FROM accounts INDEXED BY idx_accounts_code_validity \
1815 WHERE code_commitment IS NOT NULL \
1816 AND valid_until > ?1 \
1817 )",
1818 )
1819 .bind::<BigInt, _>(cutoff_block)
1820 .execute(conn)
1821 .map_err(DatabaseError::Diesel)?,
1822 };
1823
1824 diesel::insert_into(schema::prune_progress::table)
1825 .values((
1826 schema::prune_progress::id.eq(0),
1827 schema::prune_progress::codes_cutoff.eq(cutoff_block),
1828 ))
1829 .on_conflict(schema::prune_progress::id)
1830 .do_update()
1831 .set(schema::prune_progress::codes_cutoff.eq(cutoff_block))
1832 .execute(conn)
1833 .map_err(DatabaseError::Diesel)?;
1834
1835 Ok(deleted)
1836}