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_tracing::miden_instrument;
24use miden_node_utils::limiter::{
25 MAX_RESPONSE_PAYLOAD_BYTES,
26 QueryParamAccountIdLimit,
27 QueryParamLimiter,
28};
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() {
168 Some(select_full_account(conn, account_id)?)
169 } else {
170 None
171 };
172
173 Ok(AccountInfo { summary, details })
174}
175
176pub(crate) fn select_full_account(
181 conn: &mut SqliteConnection,
182 account_id: AccountId,
183) -> Result<Account, DatabaseError> {
184 let joined = schema::accounts::table.inner_join(schema::account_codes::table.on(
186 schema::accounts::code_commitment.eq(schema::account_codes::code_commitment.nullable()),
187 ));
188
189 let (nonce, code_bytes): (Option<i64>, Vec<u8>) =
190 SelectDsl::select(joined, (schema::accounts::nonce, schema::account_codes::code))
191 .filter(schema::accounts::account_id.eq(account_id.to_bytes()))
192 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
193 .get_result(conn)
194 .optional()?
195 .ok_or(DatabaseError::AccountNotFoundInDb(account_id))?;
196
197 let nonce = raw_sql_to_nonce(nonce.ok_or_else(|| {
198 DatabaseError::DataCorrupted(format!("No nonce found for account {account_id}"))
199 })?);
200
201 let code = AccountCode::read_from_bytes(&code_bytes)?;
202
203 let storage = select_latest_account_storage(conn, account_id)?;
205
206 let vault_entries: Vec<(Vec<u8>, Option<Vec<u8>>)> = SelectDsl::select(
208 schema::account_vault_assets::table,
209 (schema::account_vault_assets::vault_key, schema::account_vault_assets::asset),
210 )
211 .filter(schema::account_vault_assets::account_id.eq(account_id.to_bytes()))
212 .filter(schema::account_vault_assets::valid_until.eq(VALID_FOREVER))
213 .load(conn)?;
214
215 let mut assets = Vec::new();
216 for (_key_bytes, maybe_asset_bytes) in vault_entries {
217 if let Some(asset_bytes) = maybe_asset_bytes {
218 let asset = Asset::read_from_bytes(&asset_bytes)?;
219 assets.push(asset);
220 }
221 }
222
223 let vault = AssetVault::new(&assets)?;
224
225 Ok(Account::new(account_id, vault, storage, code, nonce, None)?)
226}
227
228#[derive(Debug)]
230pub struct AccountCommitmentsPage {
231 pub commitments: Vec<(AccountId, Word)>,
233 pub next_cursor: Option<AccountId>,
235}
236
237pub(crate) fn select_account_commitments_paged(
258 conn: &mut SqliteConnection,
259 page_size: NonZeroUsize,
260 after_account_id: Option<AccountId>,
261) -> Result<AccountCommitmentsPage, DatabaseError> {
262 #[expect(clippy::cast_possible_wrap)]
264 let limit = (page_size.get() + 1) as i64;
265
266 let mut query = SelectDsl::select(
267 schema::accounts::table,
268 (schema::accounts::account_id, schema::accounts::account_commitment),
269 )
270 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
271 .order_by(schema::accounts::account_id.asc())
272 .limit(limit)
273 .into_boxed();
274
275 if let Some(cursor) = after_account_id {
276 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
277 }
278
279 let raw = query.load::<(Vec<u8>, Vec<u8>)>(conn)?;
280
281 let mut commitments = raw
282 .into_iter()
283 .map(|(ref account, ref commitment)| {
284 Ok((AccountId::read_from_bytes(account)?, Word::read_from_bytes(commitment)?))
285 })
286 .collect::<Result<Vec<_>, DatabaseError>>()?;
287
288 let next_cursor = if commitments.len() > page_size.get() {
290 commitments.pop(); commitments.last().map(|(id, _)| *id)
292 } else {
293 None
294 };
295
296 Ok(AccountCommitmentsPage { commitments, next_cursor })
297}
298
299#[derive(Debug)]
301pub struct PublicAccountIdsPage {
302 pub account_ids: Vec<AccountId>,
304 pub next_cursor: Option<AccountId>,
306}
307
308#[derive(Debug)]
310pub struct PublicAccountStateRoots {
311 pub account_id: AccountId,
312 pub vault_root: Word,
313 pub storage_header: AccountStorageHeader,
314}
315
316#[derive(Debug)]
318pub struct PublicAccountStateRootsPage {
319 pub accounts: Vec<PublicAccountStateRoots>,
321 pub next_cursor: Option<AccountId>,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
327pub(crate) struct PrecomputedPublicAccountState {
328 pub(crate) vault_root: Word,
329 pub(crate) storage_map_roots: BTreeMap<StorageSlotName, Word>,
330}
331
332pub(crate) type PrecomputedPublicAccountStates = BTreeMap<AccountId, PrecomputedPublicAccountState>;
333
334pub(crate) fn select_public_account_ids_paged(
359 conn: &mut SqliteConnection,
360 page_size: NonZeroUsize,
361 after_account_id: Option<AccountId>,
362) -> Result<PublicAccountIdsPage, DatabaseError> {
363 #[expect(clippy::cast_possible_wrap)]
364 let limit = (page_size.get() + 1) as i64;
365
366 let mut query = SelectDsl::select(schema::accounts::table, schema::accounts::account_id)
367 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
368 .filter(schema::accounts::code_commitment.is_not_null())
369 .order_by(schema::accounts::account_id.asc())
370 .limit(limit)
371 .into_boxed();
372
373 if let Some(cursor) = after_account_id {
374 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
375 }
376
377 let raw = query.load::<Vec<u8>>(conn)?;
378
379 let mut account_ids: Vec<AccountId> = raw
380 .into_iter()
381 .map(|bytes| {
382 AccountId::read_from_bytes(&bytes).map_err(DatabaseError::DeserializationError)
383 })
384 .collect::<Result<_, _>>()?;
385
386 let next_cursor = if account_ids.len() > page_size.get() {
388 account_ids.pop(); account_ids.last().copied()
390 } else {
391 None
392 };
393
394 Ok(PublicAccountIdsPage { account_ids, next_cursor })
395}
396
397pub(crate) fn select_public_account_state_roots_paged(
424 conn: &mut SqliteConnection,
425 page_size: NonZeroUsize,
426 after_account_id: Option<AccountId>,
427) -> Result<PublicAccountStateRootsPage, DatabaseError> {
428 #[expect(clippy::cast_possible_wrap)]
429 let limit = (page_size.get() + 1) as i64;
430
431 let mut query = SelectDsl::select(
432 schema::accounts::table,
433 (
434 schema::accounts::account_id,
435 schema::accounts::vault_root,
436 schema::accounts::storage_header,
437 ),
438 )
439 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
440 .filter(schema::accounts::code_commitment.is_not_null())
441 .order_by(schema::accounts::account_id.asc())
442 .limit(limit)
443 .into_boxed();
444
445 if let Some(cursor) = after_account_id {
446 query = query.filter(schema::accounts::account_id.gt(cursor.to_bytes()));
447 }
448
449 let raw = query.load::<(Vec<u8>, Option<Vec<u8>>, Option<Vec<u8>>)>(conn)?;
450
451 let mut accounts: Vec<PublicAccountStateRoots> = raw
452 .into_iter()
453 .map(|(account_id_bytes, vault_root_bytes, storage_header_bytes)| {
454 let account_id = AccountId::read_from_bytes(&account_id_bytes)
455 .map_err(DatabaseError::DeserializationError)?;
456 let vault_root_bytes = vault_root_bytes.ok_or_else(|| {
457 DatabaseError::DataCorrupted(format!(
458 "public account {account_id} is missing a vault root"
459 ))
460 })?;
461 let storage_header_bytes = storage_header_bytes.ok_or_else(|| {
462 DatabaseError::DataCorrupted(format!(
463 "public account {account_id} is missing a storage header"
464 ))
465 })?;
466
467 Ok::<_, DatabaseError>(PublicAccountStateRoots {
468 account_id,
469 vault_root: Word::read_from_bytes(&vault_root_bytes)?,
470 storage_header: AccountStorageHeader::read_from_bytes(&storage_header_bytes)?,
471 })
472 })
473 .collect::<Result<_, _>>()?;
474
475 let next_cursor = if accounts.len() > page_size.get() {
477 accounts.pop();
478 accounts.last().map(|account| account.account_id)
479 } else {
480 None
481 };
482
483 Ok(PublicAccountStateRootsPage { accounts, next_cursor })
484}
485
486pub(crate) fn select_account_vault_assets(
514 conn: &mut SqliteConnection,
515 account_id: AccountId,
516 block_range: RangeInclusive<BlockNumber>,
517) -> Result<(BlockNumber, Vec<AccountVaultValue>), DatabaseError> {
518 use schema::account_vault_assets as t;
519 const ROW_OVERHEAD_BYTES: usize = 2 * size_of::<Word>() + size_of::<u32>(); const MAX_ROWS: usize = MAX_RESPONSE_PAYLOAD_BYTES / ROW_OVERHEAD_BYTES;
523
524 if !account_id.is_public() {
525 return Err(DatabaseError::AccountNotPublic(account_id));
526 }
527
528 if block_range.is_empty() {
529 return Err(DatabaseError::InvalidBlockRange {
530 from: *block_range.start(),
531 to: *block_range.end(),
532 });
533 }
534
535 let raw: Vec<(i64, Vec<u8>, Option<Vec<u8>>)> =
536 SelectDsl::select(t::table, (t::block_num, t::vault_key, t::asset))
537 .filter(
538 t::account_id
539 .eq(account_id.to_bytes())
540 .and(t::block_num.ge(block_range.start().to_raw_sql()))
541 .and(t::block_num.le(block_range.end().to_raw_sql())),
542 )
543 .order(t::block_num.asc())
544 .limit(i64::try_from(MAX_ROWS + 1).expect("should fit within i64"))
545 .load::<(i64, Vec<u8>, Option<Vec<u8>>)>(conn)?;
546
547 let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last()
550 && raw.len() > MAX_ROWS
551 {
552 let values = raw
553 .into_iter()
554 .take_while(|(bn, ..)| *bn != last_block_num)
555 .map(AccountVaultValue::from_raw_row)
556 .collect::<Result<Vec<_>, DatabaseError>>()?;
557
558 let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num);
559
560 (last_block_included, values)
561 } else {
562 (
563 *block_range.end(),
564 raw.into_iter().map(AccountVaultValue::from_raw_row).collect::<Result<_, _>>()?,
565 )
566 };
567
568 Ok((last_block_included, values))
569}
570
571pub(crate) fn select_account_vault_at_block(
583 conn: &mut SqliteConnection,
584 account_id: AccountId,
585 block_num: BlockNumber,
586) -> Result<Vec<Asset>, DatabaseError> {
587 use diesel::sql_types::{BigInt, Binary};
588
589 let account_id_bytes = account_id.to_bytes();
590 let block_num_sql = block_num.to_raw_sql();
591 let limit_sql =
592 i64::try_from(AccountVaultDetails::MAX_RETURN_ENTRIES + 1).expect("should fit within i64");
593
594 let entries: Vec<Option<Vec<u8>>> = diesel::sql_query(
595 r"
596 SELECT asset FROM account_vault_assets
597 WHERE account_id = ?1 AND block_num <= ?2 AND valid_until > ?2
598 LIMIT ?3
599 ",
600 )
601 .bind::<Binary, _>(&account_id_bytes)
602 .bind::<BigInt, _>(block_num_sql)
603 .bind::<BigInt, _>(limit_sql)
604 .load::<AssetRow>(conn)?
605 .into_iter()
606 .map(|row| row.asset)
607 .collect();
608
609 let mut assets = Vec::new();
611 for asset_bytes in entries.into_iter().flatten() {
612 let asset = Asset::read_from_bytes(&asset_bytes)?;
613 assets.push(asset);
614 }
615
616 Ok(assets)
617}
618
619#[derive(QueryableByName)]
620struct AssetRow {
621 #[diesel(sql_type = diesel::sql_types::Nullable<diesel::sql_types::Binary>)]
622 asset: Option<Vec<u8>>,
623}
624
625#[cfg(test)]
646pub(crate) fn select_all_accounts(
647 conn: &mut SqliteConnection,
648) -> Result<Vec<AccountInfo>, DatabaseError> {
649 let raw = SelectDsl::select(schema::accounts::table, AccountSummaryRaw::as_select())
650 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
651 .order_by(schema::accounts::block_num.asc())
652 .load::<AccountSummaryRaw>(conn)?;
653
654 let summaries: Vec<AccountSummary> = vec_raw_try_into(raw)?;
655
656 let account_infos = summaries
658 .into_iter()
659 .map(|summary| {
660 let account_id = summary.account_id;
661 let details = select_full_account(conn, account_id).ok();
662 AccountInfo { summary, details }
663 })
664 .collect();
665
666 Ok(account_infos)
667}
668
669#[derive(Debug, Clone, PartialEq, Eq)]
670pub struct StorageMapValue {
671 pub block_num: BlockNumber,
672 pub slot_name: StorageSlotName,
673 pub key: StorageMapKey,
674 pub value: Word,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub struct StorageMapValuesPage {
679 pub last_block_included: BlockNumber,
681 pub values: Vec<StorageMapValue>,
683}
684
685impl StorageMapValue {
686 pub fn from_raw_row(row: StorageMapValueRow) -> Result<Self, DatabaseError> {
687 let (block_num, slot_name, key, value) = row;
688 Ok(Self {
689 block_num: BlockNumber::from_raw_sql(block_num)?,
690 slot_name: StorageSlotName::from_raw_sql(slot_name)?,
691 key: StorageMapKey::read_from_bytes(&key)?,
692 value: Word::read_from_bytes(&value)?,
693 })
694 }
695}
696
697pub(crate) fn select_account_storage_map_values_paged(
739 conn: &mut SqliteConnection,
740 account_id: AccountId,
741 block_range: RangeInclusive<BlockNumber>,
742 limit: usize,
743) -> Result<StorageMapValuesPage, DatabaseError> {
744 use schema::account_storage_map_values as t;
745
746 if !account_id.is_public() {
747 return Err(DatabaseError::AccountNotPublic(account_id));
748 }
749
750 if block_range.is_empty() {
751 return Err(DatabaseError::InvalidBlockRange {
752 from: *block_range.start(),
753 to: *block_range.end(),
754 });
755 }
756
757 let raw: Vec<StorageMapValueRow> =
758 SelectDsl::select(t::table, (t::block_num, t::slot_name, t::key, t::value))
759 .filter(
760 t::account_id
761 .eq(account_id.to_bytes())
762 .and(t::block_num.ge(block_range.start().to_raw_sql()))
763 .and(t::block_num.le(block_range.end().to_raw_sql())),
764 )
765 .order(t::block_num.asc())
766 .limit(i64::try_from(limit + 1).expect("limit fits within i64"))
767 .load(conn)?;
768
769 let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last()
772 && raw.len() > limit
773 {
774 let values = raw
775 .into_iter()
776 .take_while(|(bn, ..)| *bn != last_block_num)
777 .map(StorageMapValue::from_raw_row)
778 .collect::<Result<Vec<_>, DatabaseError>>()?;
779
780 let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num);
781
782 (last_block_included, values)
783 } else {
784 (
785 *block_range.end(),
786 raw.into_iter()
787 .map(StorageMapValue::from_raw_row)
788 .collect::<Result<Vec<_>, _>>()?,
789 )
790 };
791
792 Ok(StorageMapValuesPage { last_block_included, values })
793}
794
795pub(crate) fn select_latest_account_storage(
801 conn: &mut SqliteConnection,
802 account_id: AccountId,
803) -> Result<AccountStorage, DatabaseError> {
804 let (storage_header, map_entries_by_slot) =
805 select_latest_account_storage_components(conn, account_id)?;
806 let slots = storage_header
808 .slots()
809 .map(|slot_header| {
810 let slot = match slot_header.slot_type() {
811 StorageSlotType::Value => {
812 StorageSlot::with_value(slot_header.name().clone(), slot_header.value())
814 },
815 StorageSlotType::Map => {
816 let entries =
818 map_entries_by_slot.get(slot_header.name()).cloned().unwrap_or_default();
819 let storage_map = StorageMap::with_entries(entries)?;
820 StorageSlot::with_map(slot_header.name().clone(), storage_map)
821 },
822 };
823 Ok(slot)
824 })
825 .collect::<Result<Vec<_>, DatabaseError>>()?;
826
827 Ok(AccountStorage::new(slots)?)
828}
829
830pub(crate) fn select_latest_account_storage_components(
832 conn: &mut SqliteConnection,
833 account_id: AccountId,
834) -> Result<StorageHeaderWithEntries, DatabaseError> {
835 let account_id_bytes = account_id.to_bytes();
836
837 let storage_blob: Option<Vec<u8>> =
839 SelectDsl::select(schema::accounts::table, schema::accounts::storage_header)
840 .filter(schema::accounts::account_id.eq(&account_id_bytes))
841 .filter(schema::accounts::valid_until.eq(VALID_FOREVER))
842 .first(conn)
843 .optional()?
844 .flatten();
845
846 let header = match storage_blob {
847 Some(blob) => AccountStorageHeader::read_from_bytes(&blob)?,
848 None => AccountStorageHeader::new(Vec::new())?,
849 };
850
851 let entries = select_latest_storage_map_entries_all(conn, &account_id)?;
852 Ok((header, entries))
853}
854
855fn select_latest_storage_map_entries_all(
857 conn: &mut SqliteConnection,
858 account_id: &AccountId,
859) -> Result<HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>, DatabaseError> {
860 use schema::account_storage_map_values as t;
861
862 let map_values: Vec<(String, Vec<u8>, Vec<u8>)> =
863 SelectDsl::select(t::table, (t::slot_name, t::key, t::value))
864 .filter(t::account_id.eq(&account_id.to_bytes()))
865 .filter(t::valid_until.eq(VALID_FOREVER))
866 .load(conn)?;
867
868 group_storage_map_entries(map_values)
869}
870
871fn group_storage_map_entries(
872 map_values: Vec<(String, Vec<u8>, Vec<u8>)>,
873) -> Result<HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>, DatabaseError> {
874 let mut map_entries_by_slot: HashMap<StorageSlotName, BTreeMap<StorageMapKey, Word>> =
875 HashMap::new();
876 for (slot_name_str, key_bytes, value_bytes) in map_values {
877 let slot_name: StorageSlotName = slot_name_str.parse().map_err(|_| {
878 DatabaseError::DataCorrupted(format!("Invalid slot name: {slot_name_str}"))
879 })?;
880 let key = StorageMapKey::read_from_bytes(&key_bytes)?;
881 let value = Word::read_from_bytes(&value_bytes)?;
882 map_entries_by_slot.entry(slot_name).or_default().insert(key, value);
883 }
884
885 Ok(map_entries_by_slot)
886}
887
888#[derive(Queryable, Selectable)]
892#[diesel(table_name = crate::db::schema::account_vault_assets)]
893#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
894pub struct AccountVaultUpdateRaw {
895 pub vault_key: Vec<u8>,
896 pub asset: Option<Vec<u8>>,
897 pub block_num: i64,
898}
899
900impl TryFrom<AccountVaultUpdateRaw> for AccountVaultValue {
901 type Error = DatabaseError;
902
903 fn try_from(raw: AccountVaultUpdateRaw) -> Result<Self, Self::Error> {
904 let vault_key = AssetId::try_from(Word::read_from_bytes(&raw.vault_key)?)?;
905 let asset = raw.asset.map(|bytes| Asset::read_from_bytes(&bytes)).transpose()?;
906 let block_num = BlockNumber::from_raw_sql(raw.block_num)?;
907
908 Ok(AccountVaultValue { block_num, vault_key, asset })
909 }
910}
911
912#[derive(Debug, Clone, PartialEq, Eq, Selectable, Queryable, QueryableByName)]
913#[diesel(table_name = schema::accounts)]
914#[diesel(check_for_backend(Sqlite))]
915pub struct AccountSummaryRaw {
916 account_id: Vec<u8>, account_commitment: Vec<u8>, block_num: i64, }
920
921impl TryInto<AccountSummary> for AccountSummaryRaw {
922 type Error = DatabaseError;
923 fn try_into(self) -> Result<AccountSummary, Self::Error> {
924 let account_id = AccountId::read_from_bytes(&self.account_id[..])?;
925 let account_commitment = Word::read_from_bytes(&self.account_commitment[..])?;
926 let block_num = BlockNumber::from_raw_sql(self.block_num)?;
927
928 Ok(AccountSummary {
929 account_id,
930 account_commitment,
931 block_num,
932 })
933 }
934}
935
936pub(crate) fn insert_account_vault_asset(
945 conn: &mut SqliteConnection,
946 account_id: AccountId,
947 block_num: BlockNumber,
948 vault_key: AssetId,
949 asset: Option<Asset>,
950) -> Result<usize, DatabaseError> {
951 let record = AccountAssetRowInsert::new(&account_id, &vault_key, block_num, asset);
952
953 diesel::Connection::transaction(conn, |conn| {
954 let vault_key: Word = vault_key.into();
956 let vault_key_bytes = vault_key.to_bytes();
957 let account_id_bytes = account_id.to_bytes();
958 let update_count = diesel::update(schema::account_vault_assets::table)
959 .filter(
960 schema::account_vault_assets::account_id
961 .eq(account_id_bytes)
962 .and(schema::account_vault_assets::vault_key.eq(vault_key_bytes))
963 .and(schema::account_vault_assets::valid_until.eq(VALID_FOREVER)),
964 )
965 .set(schema::account_vault_assets::valid_until.eq(block_num.to_raw_sql()))
966 .execute(conn)?;
967
968 let insert_count = diesel::insert_into(schema::account_vault_assets::table)
970 .values(record)
971 .execute(conn)?;
972
973 Ok(update_count + insert_count)
974 })
975}
976
977pub(crate) fn insert_account_storage_map_value(
990 conn: &mut SqliteConnection,
991 account_id: AccountId,
992 block_num: BlockNumber,
993 slot_name: StorageSlotName,
994 key: StorageMapKey,
995 value: Word,
996) -> Result<usize, DatabaseError> {
997 insert_account_storage_map_value_inner(conn, account_id, block_num, slot_name, key, value, true)
998}
999
1000fn insert_account_storage_map_value_inner(
1013 conn: &mut SqliteConnection,
1014 account_id: AccountId,
1015 block_num: BlockNumber,
1016 slot_name: StorageSlotName,
1017 key: StorageMapKey,
1018 value: Word,
1019 invalidate_previous: bool,
1020) -> Result<usize, DatabaseError> {
1021 let account_id = account_id.to_bytes();
1022 let key = key.to_bytes();
1023 let value = value.to_bytes();
1024 let slot_name = slot_name.to_raw_sql();
1025 let block_num = block_num.to_raw_sql();
1026
1027 let update_count = if invalidate_previous {
1028 diesel::update(schema::account_storage_map_values::table)
1029 .filter(
1030 schema::account_storage_map_values::account_id
1031 .eq(&account_id)
1032 .and(schema::account_storage_map_values::slot_name.eq(&slot_name))
1033 .and(schema::account_storage_map_values::key.eq(&key))
1034 .and(schema::account_storage_map_values::valid_until.eq(VALID_FOREVER)),
1035 )
1036 .set(schema::account_storage_map_values::valid_until.eq(block_num))
1037 .execute(conn)?
1038 } else {
1039 0
1040 };
1041
1042 let record = AccountStorageMapRowInsert {
1043 account_id,
1044 key,
1045 value,
1046 slot_name,
1047 block_num,
1048 valid_until: VALID_FOREVER,
1049 };
1050 let insert_count = diesel::insert_into(schema::account_storage_map_values::table)
1051 .values(record)
1052 .execute(conn)?;
1053
1054 Ok(update_count + insert_count)
1055}
1056
1057type PendingStorageInserts = Vec<(AccountId, StorageSlotName, StorageMapKey, Word)>;
1058type PendingAssetInserts = Vec<(AccountId, AssetId, Option<Asset>)>;
1059
1060fn prepare_full_account_update(
1061 update: &BlockAccountUpdate,
1062 account: Account,
1063) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
1064 let account_id = account.id();
1065
1066 if account.to_commitment() != update.final_state_commitment() {
1068 return Err(DatabaseError::AccountCommitmentsMismatch {
1069 calculated: account.to_commitment(),
1070 expected: update.final_state_commitment(),
1071 });
1072 }
1073
1074 let mut storage = Vec::new();
1076 for slot in account.storage().slots() {
1077 if let StorageSlotContent::Map(storage_map) = slot.content() {
1078 for (key, value) in storage_map.entries() {
1079 storage.push((account_id, slot.name().clone(), *key, *value));
1080 }
1081 }
1082 }
1083
1084 let mut assets = Vec::new();
1086 for asset in account.vault().assets() {
1087 let should_insert = match asset {
1089 Asset::Fungible(fungible) => fungible.amount().as_u64() > 0,
1090 Asset::NonFungible(_) => true,
1091 };
1092 if should_insert {
1093 assets.push((account_id, asset.id(), Some(asset)));
1094 }
1095 }
1096
1097 Ok((AccountStateForInsert::FullAccount(account), storage, assets))
1098}
1099
1100fn prepare_precomputed_full_account_update(
1113 update: &BlockAccountUpdate,
1114 patch: &AccountPatch,
1115 precomputed: &PrecomputedPublicAccountState,
1116) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
1117 let account_id = patch.id();
1118 let code = patch.code().cloned().ok_or_else(|| {
1119 DatabaseError::DataCorrupted(format!(
1120 "full-state patch for account {account_id} is missing account code"
1121 ))
1122 })?;
1123 let nonce = patch.final_nonce().ok_or_else(|| {
1124 DatabaseError::DataCorrupted(format!(
1125 "full-state patch for account {account_id} is missing final nonce"
1126 ))
1127 })?;
1128
1129 let storage_header = apply_storage_patch_with_roots(
1130 &AccountStorageHeader::new(Vec::new())?,
1131 patch.storage(),
1132 &precomputed.storage_map_roots,
1133 )?;
1134 let account_header = miden_protocol::account::AccountHeader::new(
1135 account_id,
1136 nonce,
1137 precomputed.vault_root,
1138 storage_header.to_commitment(),
1139 code.commitment(),
1140 );
1141 if account_header.to_commitment() != update.final_state_commitment() {
1142 return Err(DatabaseError::AccountCommitmentsMismatch {
1143 calculated: account_header.to_commitment(),
1144 expected: update.final_state_commitment(),
1145 });
1146 }
1147
1148 let storage = patch
1149 .storage()
1150 .maps()
1151 .flat_map(|(slot_name, map_patch)| {
1152 map_patch.entries().into_iter().flat_map(move |entries| {
1153 entries
1154 .as_map()
1155 .iter()
1156 .filter(|(_key, value)| **value != Word::empty())
1157 .map(move |(key, value)| (account_id, slot_name.clone(), *key, *value))
1158 })
1159 })
1160 .collect();
1161 let assets = patch
1162 .vault()
1163 .iter()
1164 .filter(|(_asset_id, value)| **value != Word::empty())
1165 .map(|(asset_id, value)| {
1166 Asset::from_id_and_value(*asset_id, *value)
1167 .map(|asset| (account_id, *asset_id, Some(asset)))
1168 })
1169 .collect::<Result<Vec<_>, _>>()?;
1170
1171 let is_network_account = NetworkAccount::new(Account::try_from(patch)?).is_ok();
1174 let state = PrecomputedFullAccountState {
1175 nonce,
1176 code,
1177 storage_header,
1178 vault_root: precomputed.vault_root,
1179 is_network_account,
1180 };
1181
1182 Ok((AccountStateForInsert::PrecomputedFullState(state), storage, assets))
1183}
1184
1185fn prepare_partial_account_update(
1197 update: &BlockAccountUpdate,
1198 account_id: AccountId,
1199 patch: &AccountPatch,
1200 precomputed: &PrecomputedPublicAccountState,
1201 existing: &LatestAccountStateRow,
1202) -> Result<(AccountStateForInsert, PendingStorageInserts, PendingAssetInserts), DatabaseError> {
1203 let state_headers = existing.state_headers(account_id)?;
1206
1207 let mut assets = Vec::new();
1210 for (vault_key, value) in patch.vault().iter() {
1211 let update_or_remove = if *value == Word::empty() {
1212 None
1213 } else {
1214 Some(Asset::from_id_and_value(*vault_key, *value)?)
1215 };
1216 assets.push((account_id, *vault_key, update_or_remove));
1217 }
1218
1219 let mut storage = Vec::new();
1222 for (slot_name, map_patch) in patch.storage().maps() {
1223 for (key, value) in map_patch.entries().into_iter().flat_map(StorageMapPatchEntries::as_map)
1224 {
1225 storage.push((account_id, slot_name.clone(), *key, *value));
1226 }
1227 }
1228
1229 let new_storage_header = apply_storage_patch_with_roots(
1231 &state_headers.storage_header,
1232 patch.storage(),
1233 &precomputed.storage_map_roots,
1234 )?;
1235
1236 let new_vault_root = precomputed.vault_root;
1237
1238 let new_nonce = patch.final_nonce().unwrap_or(state_headers.nonce);
1240
1241 let account_state = PartialAccountState {
1243 nonce: new_nonce,
1244 code_commitment: state_headers.code_commitment,
1245 storage_header: new_storage_header,
1246 vault_root: new_vault_root,
1247 };
1248
1249 let account_header = miden_protocol::account::AccountHeader::new(
1250 account_id,
1251 account_state.nonce,
1252 account_state.vault_root,
1253 account_state.storage_header.to_commitment(),
1254 account_state.code_commitment,
1255 );
1256
1257 if account_header.to_commitment() != update.final_state_commitment() {
1258 return Err(DatabaseError::AccountCommitmentsMismatch {
1259 calculated: account_header.to_commitment(),
1260 expected: update.final_state_commitment(),
1261 });
1262 }
1263
1264 Ok((AccountStateForInsert::PartialState(account_state), storage, assets))
1265}
1266
1267pub(crate) fn select_network_accounts_subset(
1271 conn: &mut SqliteConnection,
1272 account_ids: &[AccountId],
1273) -> Result<HashSet<AccountId>, DatabaseError> {
1274 QueryParamAccountIdLimit::check(account_ids.len())?;
1275 let id_bytes: Vec<Vec<u8>> =
1276 account_ids.iter().map(miden_crypto::utils::Serializable::to_bytes).collect();
1277
1278 let rows: Vec<Vec<u8>> =
1279 SelectDsl::select(schema::accounts::table, schema::accounts::account_id)
1280 .filter(
1281 schema::accounts::account_id
1282 .eq_any(&id_bytes)
1283 .and(
1284 schema::accounts::network_account_type
1285 .eq(NetworkAccountType::Network.to_raw_sql()),
1286 )
1287 .and(schema::accounts::valid_until.eq(VALID_FOREVER)),
1288 )
1289 .load::<Vec<u8>>(conn)
1290 .map_err(DatabaseError::Diesel)?;
1291
1292 rows.into_iter()
1293 .map(|bytes| {
1294 AccountId::read_from_bytes(&bytes).map_err(DatabaseError::DeserializationError)
1295 })
1296 .collect()
1297}
1298
1299#[miden_instrument(
1301 target = COMPONENT,
1302 err,
1303)]
1304pub(crate) fn upsert_accounts(
1305 conn: &mut SqliteConnection,
1306 accounts: &[BlockAccountUpdate],
1307 block_num: BlockNumber,
1308 precomputed_public_states: &PrecomputedPublicAccountStates,
1309) -> Result<usize, DatabaseError> {
1310 let mut count = 0;
1311 for update in accounts {
1312 let account_id = update.account_id();
1313 let account_id_bytes = account_id.to_bytes();
1314
1315 let existing = select_latest_account_state(conn, account_id)?;
1318 let account_is_new = existing.is_none();
1319
1320 let created_at_block = match &existing {
1321 Some(row) => row.created_at_block()?,
1322 None => block_num,
1323 };
1324
1325 let (account_state, pending_storage_inserts, pending_asset_inserts) = match update.details()
1330 {
1331 AccountUpdateDetails::Private => (AccountStateForInsert::Private, vec![], vec![]),
1332
1333 AccountUpdateDetails::Public(patch) if patch.is_full_state() => {
1335 if block_num == BlockNumber::GENESIS {
1336 let account = Account::try_from(patch)
1337 .expect("Patch to full account always works for full state patches");
1338 debug_assert_eq!(account_id, account.id());
1339 prepare_full_account_update(update, account)?
1340 } else {
1341 let precomputed =
1342 precomputed_public_states.get(&account_id).ok_or_else(|| {
1343 DatabaseError::DataCorrupted(format!(
1344 "missing precomputed public account state for account {account_id}"
1345 ))
1346 })?;
1347 prepare_precomputed_full_account_update(update, patch, precomputed)?
1348 }
1349 },
1350
1351 AccountUpdateDetails::Public(patch) => {
1353 let precomputed = precomputed_public_states.get(&account_id).ok_or_else(|| {
1354 DatabaseError::DataCorrupted(format!(
1355 "missing precomputed public account state for account {account_id}"
1356 ))
1357 })?;
1358 let existing =
1359 existing.as_ref().ok_or(DatabaseError::AccountNotFoundInDb(account_id))?;
1360 prepare_partial_account_update(update, account_id, patch, precomputed, existing)?
1361 },
1362 };
1363
1364 let network_account_type = match &existing {
1367 Some(row) => row.network_account_type()?,
1368 None => match &account_state {
1369 AccountStateForInsert::FullAccount(account)
1370 if NetworkAccount::new(account.clone()).is_ok() =>
1371 {
1372 NetworkAccountType::Network
1373 },
1374 AccountStateForInsert::PrecomputedFullState(state) if state.is_network_account => {
1375 NetworkAccountType::Network
1376 },
1377 _ => NetworkAccountType::None,
1378 },
1379 };
1380
1381 if let AccountStateForInsert::FullAccount(ref account) = account_state {
1383 let code = account.code();
1384 let code_value = AccountCodeRowInsert {
1385 code_commitment: code.commitment().to_bytes(),
1386 code: code.to_bytes(),
1387 };
1388 diesel::insert_into(schema::account_codes::table)
1389 .values(&code_value)
1390 .on_conflict(schema::account_codes::code_commitment)
1391 .do_nothing()
1392 .execute(conn)?;
1393 }
1394 if let AccountStateForInsert::PrecomputedFullState(ref state) = account_state {
1395 let code_value = AccountCodeRowInsert {
1396 code_commitment: state.code.commitment().to_bytes(),
1397 code: state.code.to_bytes(),
1398 };
1399 diesel::insert_into(schema::account_codes::table)
1400 .values(&code_value)
1401 .on_conflict(schema::account_codes::code_commitment)
1402 .do_nothing()
1403 .execute(conn)?;
1404 }
1405
1406 diesel::update(schema::accounts::table)
1408 .filter(
1409 schema::accounts::account_id
1410 .eq(&account_id_bytes)
1411 .and(schema::accounts::valid_until.eq(VALID_FOREVER)),
1412 )
1413 .set(schema::accounts::valid_until.eq(block_num.to_raw_sql()))
1414 .execute(conn)?;
1415
1416 let account_value = match &account_state {
1417 AccountStateForInsert::Private => AccountRowInsert::new_private(
1418 account_id,
1419 network_account_type,
1420 update.final_state_commitment(),
1421 block_num,
1422 created_at_block,
1423 ),
1424 AccountStateForInsert::FullAccount(account) => AccountRowInsert::new_from_account(
1425 account_id,
1426 network_account_type,
1427 update.final_state_commitment(),
1428 block_num,
1429 created_at_block,
1430 account,
1431 ),
1432 AccountStateForInsert::PrecomputedFullState(state) => {
1433 AccountRowInsert::new_from_precomputed_full_state(
1434 account_id,
1435 network_account_type,
1436 update.final_state_commitment(),
1437 block_num,
1438 created_at_block,
1439 state,
1440 )
1441 },
1442 AccountStateForInsert::PartialState(state) => AccountRowInsert::new_from_partial(
1443 account_id,
1444 network_account_type,
1445 update.final_state_commitment(),
1446 block_num,
1447 created_at_block,
1448 state,
1449 ),
1450 };
1451
1452 diesel::insert_into(schema::accounts::table)
1453 .values(&account_value)
1454 .on_conflict((schema::accounts::account_id, schema::accounts::block_num))
1455 .do_update()
1456 .set(&account_value)
1457 .execute(conn)?;
1458
1459 for (acc_id, slot_name, key, value) in pending_storage_inserts {
1460 if account_is_new {
1461 insert_account_storage_map_value_inner(
1462 conn, acc_id, block_num, slot_name, key, value, false,
1463 )?;
1464 } else {
1465 insert_account_storage_map_value(conn, acc_id, block_num, slot_name, key, value)?;
1466 }
1467 }
1468
1469 for (acc_id, vault_key, update) in pending_asset_inserts {
1470 insert_account_vault_asset(conn, acc_id, block_num, vault_key, update)?;
1471 }
1472
1473 count += 1;
1474 }
1475
1476 Ok(count)
1477}
1478
1479#[derive(Insertable, Debug, Clone)]
1480#[diesel(table_name = schema::account_codes)]
1481pub(crate) struct AccountCodeRowInsert {
1482 pub(crate) code_commitment: Vec<u8>,
1483 pub(crate) code: Vec<u8>,
1484}
1485
1486#[derive(Insertable, AsChangeset, Debug, Clone)]
1487#[diesel(table_name = schema::accounts)]
1488pub(crate) struct AccountRowInsert {
1489 pub(crate) account_id: Vec<u8>,
1490 pub(crate) network_account_type: i32,
1491 pub(crate) block_num: i64,
1492 pub(crate) account_commitment: Vec<u8>,
1493 pub(crate) code_commitment: Option<Vec<u8>>,
1494 pub(crate) nonce: Option<i64>,
1495 pub(crate) storage_header: Option<Vec<u8>>,
1496 pub(crate) vault_root: Option<Vec<u8>>,
1497 pub(crate) created_at_block: i64,
1498 pub(crate) valid_until: i64,
1499}
1500
1501impl AccountRowInsert {
1502 pub(crate) fn new_private(
1504 account_id: AccountId,
1505 network_account_type: NetworkAccountType,
1506 account_commitment: Word,
1507 block_num: BlockNumber,
1508 created_at_block: BlockNumber,
1509 ) -> Self {
1510 Self {
1511 account_id: account_id.to_bytes(),
1512 network_account_type: network_account_type.to_raw_sql(),
1513 account_commitment: account_commitment.to_bytes(),
1514 block_num: block_num.to_raw_sql(),
1515 nonce: None,
1516 code_commitment: None,
1517 storage_header: None,
1518 vault_root: None,
1519 created_at_block: created_at_block.to_raw_sql(),
1520 valid_until: VALID_FOREVER,
1521 }
1522 }
1523
1524 fn new_from_account(
1526 account_id: AccountId,
1527 network_account_type: NetworkAccountType,
1528 account_commitment: Word,
1529 block_num: BlockNumber,
1530 created_at_block: BlockNumber,
1531 account: &Account,
1532 ) -> Self {
1533 Self {
1534 account_id: account_id.to_bytes(),
1535 network_account_type: network_account_type.to_raw_sql(),
1536 account_commitment: account_commitment.to_bytes(),
1537 block_num: block_num.to_raw_sql(),
1538 nonce: Some(nonce_to_raw_sql(account.nonce())),
1539 code_commitment: Some(account.code().commitment().to_bytes()),
1540 storage_header: Some(account.storage().to_header().to_bytes()),
1541 vault_root: Some(account.vault().root().to_bytes()),
1542 created_at_block: created_at_block.to_raw_sql(),
1543 valid_until: VALID_FOREVER,
1544 }
1545 }
1546
1547 fn new_from_precomputed_full_state(
1548 account_id: AccountId,
1549 network_account_type: NetworkAccountType,
1550 account_commitment: Word,
1551 block_num: BlockNumber,
1552 created_at_block: BlockNumber,
1553 state: &PrecomputedFullAccountState,
1554 ) -> Self {
1555 Self {
1556 account_id: account_id.to_bytes(),
1557 network_account_type: network_account_type.to_raw_sql(),
1558 block_num: block_num.to_raw_sql(),
1559 account_commitment: account_commitment.to_bytes(),
1560 code_commitment: Some(state.code.commitment().to_bytes()),
1561 nonce: Some(nonce_to_raw_sql(state.nonce)),
1562 storage_header: Some(state.storage_header.to_bytes()),
1563 vault_root: Some(state.vault_root.to_bytes()),
1564 created_at_block: created_at_block.to_raw_sql(),
1565 valid_until: VALID_FOREVER,
1566 }
1567 }
1568
1569 fn new_from_partial(
1571 account_id: AccountId,
1572 network_account_type: NetworkAccountType,
1573 account_commitment: Word,
1574 block_num: BlockNumber,
1575 created_at_block: BlockNumber,
1576 state: &PartialAccountState,
1577 ) -> Self {
1578 Self {
1579 account_id: account_id.to_bytes(),
1580 network_account_type: network_account_type.to_raw_sql(),
1581 account_commitment: account_commitment.to_bytes(),
1582 block_num: block_num.to_raw_sql(),
1583 nonce: Some(nonce_to_raw_sql(state.nonce)),
1584 code_commitment: Some(state.code_commitment.to_bytes()),
1585 storage_header: Some(state.storage_header.to_bytes()),
1586 vault_root: Some(state.vault_root.to_bytes()),
1587 created_at_block: created_at_block.to_raw_sql(),
1588 valid_until: VALID_FOREVER,
1589 }
1590 }
1591}
1592
1593#[derive(Insertable, AsChangeset, Debug, Clone)]
1594#[diesel(table_name = schema::account_vault_assets)]
1595pub(crate) struct AccountAssetRowInsert {
1596 pub(crate) account_id: Vec<u8>,
1597 pub(crate) block_num: i64,
1598 pub(crate) vault_key: Vec<u8>,
1599 pub(crate) asset: Option<Vec<u8>>,
1600 pub(crate) valid_until: i64,
1601}
1602
1603impl AccountAssetRowInsert {
1604 pub(crate) fn new(
1605 account_id: &AccountId,
1606 vault_key: &AssetId,
1607 block_num: BlockNumber,
1608 asset: Option<Asset>,
1609 ) -> Self {
1610 let account_id = account_id.to_bytes();
1611 let vault_key: Word = (*vault_key).into();
1612 let vault_key = vault_key.to_bytes();
1613 let block_num = block_num.to_raw_sql();
1614 let asset = asset.map(|asset| asset.to_bytes());
1615 Self {
1616 account_id,
1617 block_num,
1618 vault_key,
1619 asset,
1620 valid_until: VALID_FOREVER,
1621 }
1622 }
1623}
1624
1625#[derive(Insertable, AsChangeset, Debug, Clone)]
1626#[diesel(table_name = schema::account_storage_map_values)]
1627pub(crate) struct AccountStorageMapRowInsert {
1628 pub(crate) account_id: Vec<u8>,
1629 pub(crate) block_num: i64,
1630 pub(crate) slot_name: String,
1631 pub(crate) key: Vec<u8>,
1632 pub(crate) value: Vec<u8>,
1633 pub(crate) valid_until: i64,
1634}
1635
1636pub const HISTORICAL_BLOCK_RETENTION: u32 = 50;
1644
1645#[miden_instrument(
1658 target = COMPONENT,
1659 err,
1660 fields(
1661 cutoff_block,
1662 ),
1663)]
1664pub(crate) fn prune_history(
1665 conn: &mut SqliteConnection,
1666 prune_tip: BlockNumber,
1667) -> Result<(usize, usize, usize), DatabaseError> {
1668 let cutoff_block = i64::from(prune_tip.as_u32().saturating_sub(HISTORICAL_BLOCK_RETENTION));
1669 miden_node_tracing::Span::current().record("cutoff_block", cutoff_block);
1670 let vault_deleted = prune_account_vault_assets(conn, cutoff_block)?;
1671 let storage_deleted = prune_account_storage_map_values(conn, cutoff_block)?;
1672 let codes_deleted = prune_account_codes(conn, cutoff_block)?;
1673
1674 Ok((vault_deleted, storage_deleted, codes_deleted))
1675}
1676
1677#[miden_instrument(
1678 target = COMPONENT,
1679 err,
1680 fields(
1681 cutoff_block,
1682 ),
1683)]
1684fn prune_account_vault_assets(
1685 conn: &mut SqliteConnection,
1686 cutoff_block: i64,
1687) -> Result<usize, DatabaseError> {
1688 use diesel::sql_types::BigInt;
1689
1690 diesel::sql_query(format!(
1693 "DELETE FROM account_vault_assets \
1694 WHERE valid_until != {VALID_FOREVER} \
1695 AND valid_until <= ?1"
1696 ))
1697 .bind::<BigInt, _>(cutoff_block)
1698 .execute(conn)
1699 .map_err(DatabaseError::Diesel)
1700}
1701
1702#[miden_instrument(
1703 target = COMPONENT,
1704 err,
1705 fields(
1706 cutoff_block,
1707 ),
1708)]
1709fn prune_account_storage_map_values(
1710 conn: &mut SqliteConnection,
1711 cutoff_block: i64,
1712) -> Result<usize, DatabaseError> {
1713 use diesel::sql_types::BigInt;
1714
1715 diesel::sql_query(format!(
1718 "DELETE FROM account_storage_map_values \
1719 WHERE valid_until != {VALID_FOREVER} \
1720 AND valid_until <= ?1"
1721 ))
1722 .bind::<BigInt, _>(cutoff_block)
1723 .execute(conn)
1724 .map_err(DatabaseError::Diesel)
1725}
1726
1727#[miden_instrument(
1755 target = COMPONENT,
1756 err,
1757 fields(
1758 cutoff_block,
1759 ),
1760)]
1761fn prune_account_codes(
1762 conn: &mut SqliteConnection,
1763 cutoff_block: i64,
1764) -> Result<usize, DatabaseError> {
1765 use diesel::sql_types::BigInt;
1766
1767 let prev_cutoff: Option<i64> =
1768 SelectDsl::select(schema::prune_progress::table, schema::prune_progress::codes_cutoff)
1769 .first(conn)
1770 .optional()
1771 .map_err(DatabaseError::Diesel)?;
1772
1773 let deleted = match prev_cutoff {
1774 Some(prev_cutoff) if prev_cutoff >= cutoff_block => return Ok(0),
1781 Some(prev_cutoff) => diesel::sql_query(
1782 "DELETE FROM account_codes \
1783 WHERE code_commitment IN ( \
1784 SELECT DISTINCT code_commitment \
1785 FROM accounts INDEXED BY idx_accounts_code_validity \
1786 WHERE code_commitment IS NOT NULL \
1787 AND valid_until > ?1 \
1788 AND valid_until <= ?2 \
1789 ) \
1790 AND NOT EXISTS ( \
1791 SELECT 1 \
1792 FROM accounts INDEXED BY idx_accounts_code_probe \
1793 WHERE accounts.code_commitment = account_codes.code_commitment \
1794 AND accounts.valid_until > ?2 \
1795 )",
1796 )
1797 .bind::<BigInt, _>(prev_cutoff)
1798 .bind::<BigInt, _>(cutoff_block)
1799 .execute(conn)
1800 .map_err(DatabaseError::Diesel)?,
1801 None => diesel::sql_query(
1805 "DELETE FROM account_codes \
1806 WHERE code_commitment NOT IN ( \
1807 SELECT DISTINCT code_commitment \
1808 FROM accounts INDEXED BY idx_accounts_code_validity \
1809 WHERE code_commitment IS NOT NULL \
1810 AND valid_until > ?1 \
1811 )",
1812 )
1813 .bind::<BigInt, _>(cutoff_block)
1814 .execute(conn)
1815 .map_err(DatabaseError::Diesel)?,
1816 };
1817
1818 diesel::insert_into(schema::prune_progress::table)
1819 .values((
1820 schema::prune_progress::id.eq(0),
1821 schema::prune_progress::codes_cutoff.eq(cutoff_block),
1822 ))
1823 .on_conflict(schema::prune_progress::id)
1824 .do_update()
1825 .set(schema::prune_progress::codes_cutoff.eq(cutoff_block))
1826 .execute(conn)
1827 .map_err(DatabaseError::Diesel)?;
1828
1829 Ok(deleted)
1830}