1use std::collections::BTreeMap;
4use std::rc::Rc;
5use std::string::{String, ToString};
6use std::sync::{Arc, RwLock};
7use std::vec::Vec;
8
9use miden_client::account::{
10 Account,
11 AccountCode,
12 AccountDelta,
13 AccountHeader,
14 AccountId,
15 AccountStorage,
16 Address,
17 PartialAccount,
18 PartialStorage,
19 PartialStorageMap,
20 StorageMap,
21 StorageMapKey,
22 StorageSlotName,
23 StorageSlotType,
24};
25use miden_client::asset::{Asset, AssetVault, AssetWitness, FungibleAsset};
26use miden_client::store::{
27 AccountRecord,
28 AccountRecordData,
29 AccountSmtForest,
30 AccountStatus,
31 AccountStorageFilter,
32 StoreError,
33};
34use miden_client::sync::NoteTagRecord;
35use miden_client::utils::Serializable;
36use miden_client::{AccountError, Felt, Word};
37use miden_protocol::account::{AccountStorageHeader, StorageMapWitness, StorageSlotHeader};
38use miden_protocol::asset::{AssetVaultKey, PartialVault};
39use miden_protocol::crypto::merkle::MerkleError;
40use rusqlite::types::Value;
41use rusqlite::{Connection, OptionalExtension, Transaction, named_params, params};
42
43use crate::account::helpers::{
44 query_account_addresses,
45 query_account_code,
46 query_historical_account_headers,
47 query_latest_account_headers,
48 query_storage_slots,
49 query_storage_values,
50 query_vault_assets,
51};
52use crate::sql_error::SqlResultExt;
53use crate::sync::{add_note_tag_tx, remove_note_tag_tx};
54use crate::{SqliteStore, column_value_as_u64, insert_sql, subst, u64_to_value};
55
56impl SqliteStore {
57 pub(crate) fn get_account_ids(conn: &mut Connection) -> Result<Vec<AccountId>, StoreError> {
61 const QUERY: &str = "SELECT id FROM latest_account_headers";
62
63 conn.prepare_cached(QUERY)
64 .into_store_error()?
65 .query_map([], |row| row.get(0))
66 .expect("no binding parameters used in query")
67 .map(|result| {
68 let id: String = result.map_err(|e| StoreError::ParsingError(e.to_string()))?;
69 Ok(AccountId::from_hex(&id).expect("account id is valid"))
70 })
71 .collect::<Result<Vec<AccountId>, StoreError>>()
72 }
73
74 pub(crate) fn get_account_headers(
75 conn: &mut Connection,
76 ) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
77 query_latest_account_headers(conn, "1=1 ORDER BY id", params![])
78 }
79
80 pub(crate) fn get_account_header(
81 conn: &mut Connection,
82 account_id: AccountId,
83 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
84 Ok(query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?.pop())
85 }
86
87 pub(crate) fn get_account_header_by_commitment(
88 conn: &mut Connection,
89 account_commitment: Word,
90 ) -> Result<Option<AccountHeader>, StoreError> {
91 let account_commitment_str: String = account_commitment.to_string();
92 Ok(query_historical_account_headers(
93 conn,
94 "account_commitment = ?",
95 params![account_commitment_str],
96 )?
97 .pop()
98 .map(|(header, _)| header))
99 }
100
101 pub(crate) fn get_account(
103 conn: &mut Connection,
104 account_id: AccountId,
105 ) -> Result<Option<AccountRecord>, StoreError> {
106 let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
107 return Ok(None);
108 };
109
110 let assets = query_vault_assets(conn, account_id)?;
111 let vault = AssetVault::new(&assets)?;
112
113 let slots = query_storage_slots(conn, account_id, &AccountStorageFilter::All)?
114 .into_values()
115 .collect();
116
117 let storage = AccountStorage::new(slots)?;
118
119 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
120 return Ok(None);
121 };
122
123 let account = Account::new_unchecked(
124 header.id(),
125 vault,
126 storage,
127 account_code,
128 header.nonce(),
129 status.seed().copied(),
130 );
131
132 let account_data = AccountRecordData::Full(account);
133 Ok(Some(AccountRecord::new(account_data, status)))
134 }
135
136 pub(crate) fn get_minimal_partial_account(
138 conn: &mut Connection,
139 account_id: AccountId,
140 ) -> Result<Option<AccountRecord>, StoreError> {
141 let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
142 return Ok(None);
143 };
144
145 let partial_vault = PartialVault::new(header.vault_root());
147
148 let mut storage_header = Vec::new();
150 let mut maps = vec![];
151
152 let storage_values = query_storage_values(conn, account_id)?;
153
154 for (slot_name, (slot_type, value)) in storage_values {
158 storage_header.push(StorageSlotHeader::new(slot_name.clone(), slot_type, value));
159 if slot_type == StorageSlotType::Map {
160 maps.push(PartialStorageMap::new(value));
161 }
162 }
163 storage_header.sort_by_key(StorageSlotHeader::id);
164 let storage_header =
165 AccountStorageHeader::new(storage_header).map_err(StoreError::AccountError)?;
166 let partial_storage =
167 PartialStorage::new(storage_header, maps).map_err(StoreError::AccountError)?;
168
169 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
170 return Ok(None);
171 };
172
173 let partial_account = PartialAccount::new(
174 header.id(),
175 header.nonce(),
176 account_code,
177 partial_storage,
178 partial_vault,
179 status.seed().copied(),
180 )?;
181 let account_record_data = AccountRecordData::Partial(partial_account);
182 Ok(Some(AccountRecord::new(account_record_data, status)))
183 }
184
185 pub fn get_foreign_account_code(
186 conn: &mut Connection,
187 account_ids: Vec<AccountId>,
188 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
189 let params: Vec<Value> =
190 account_ids.into_iter().map(|id| Value::from(id.to_hex())).collect();
191 const QUERY: &str = "
192 SELECT account_id, code
193 FROM foreign_account_code JOIN account_code ON foreign_account_code.code_commitment = account_code.commitment
194 WHERE account_id IN rarray(?)";
195
196 conn.prepare_cached(QUERY)
197 .into_store_error()?
198 .query_map([Rc::new(params)], |row| Ok((row.get(0)?, row.get(1)?)))
199 .expect("no binding parameters used in query")
200 .map(|result| {
201 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
202 |(id, code): (String, Vec<u8>)| {
203 Ok((
204 AccountId::from_hex(&id).map_err(|err| {
205 StoreError::AccountError(
206 AccountError::FinalAccountHeaderIdParsingFailed(err),
207 )
208 })?,
209 AccountCode::from_bytes(&code).map_err(StoreError::AccountError)?,
210 ))
211 },
212 )
213 })
214 .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
215 }
216
217 pub fn get_account_vault(
219 conn: &Connection,
220 account_id: AccountId,
221 ) -> Result<AssetVault, StoreError> {
222 let assets = query_vault_assets(conn, account_id)?;
223 Ok(AssetVault::new(&assets)?)
224 }
225
226 pub fn get_account_storage(
228 conn: &Connection,
229 account_id: AccountId,
230 filter: &AccountStorageFilter,
231 ) -> Result<AccountStorage, StoreError> {
232 let slots = query_storage_slots(conn, account_id, filter)?.into_values().collect();
233 Ok(AccountStorage::new(slots)?)
234 }
235
236 pub(crate) fn get_account_asset(
239 conn: &mut Connection,
240 smt_forest: &Arc<RwLock<AccountSmtForest>>,
241 account_id: AccountId,
242 vault_key: AssetVaultKey,
243 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
244 let header = Self::get_account_header(conn, account_id)?
245 .ok_or(StoreError::AccountDataNotFound(account_id))?
246 .0;
247
248 let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
249 match smt_forest.get_asset_and_witness(header.vault_root(), vault_key) {
250 Ok((asset, witness)) => Ok(Some((asset, witness))),
251 Err(StoreError::MerkleStoreError(MerkleError::UntrackedKey(_))) => Ok(None),
252 Err(err) => Err(err),
253 }
254 }
255
256 pub(crate) fn get_account_map_item(
259 conn: &mut Connection,
260 smt_forest: &Arc<RwLock<AccountSmtForest>>,
261 account_id: AccountId,
262 slot_name: StorageSlotName,
263 key: StorageMapKey,
264 ) -> Result<(Word, StorageMapWitness), StoreError> {
265 let header = Self::get_account_header(conn, account_id)?
266 .ok_or(StoreError::AccountDataNotFound(account_id))?
267 .0;
268
269 let mut storage_values = query_storage_values(conn, account_id)?;
270 let (slot_type, map_root) = storage_values
271 .remove(&slot_name)
272 .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))?;
273 if slot_type != StorageSlotType::Map {
274 return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name)));
275 }
276
277 let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
278 let witness = smt_forest.get_storage_map_item_witness(map_root, key)?;
279 let item = witness.get(key).unwrap_or(miden_client::EMPTY_WORD);
280
281 Ok((item, witness))
282 }
283
284 pub(crate) fn get_account_addresses(
285 conn: &mut Connection,
286 account_id: AccountId,
287 ) -> Result<Vec<Address>, StoreError> {
288 query_account_addresses(conn, account_id)
289 }
290
291 pub(crate) fn get_account_code_by_id(
293 conn: &mut Connection,
294 account_id: AccountId,
295 ) -> Result<Option<AccountCode>, StoreError> {
296 let Some((header, _)) =
297 query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?
298 .into_iter()
299 .next()
300 else {
301 return Ok(None);
302 };
303
304 query_account_code(conn, header.code_commitment())
305 }
306
307 pub(crate) fn insert_account(
311 conn: &mut Connection,
312 smt_forest: &Arc<RwLock<AccountSmtForest>>,
313 account: &Account,
314 initial_address: &Address,
315 ) -> Result<(), StoreError> {
316 let tx = conn.transaction().into_store_error()?;
317
318 Self::insert_account_code(&tx, account.code())?;
319
320 let account_id = account.id();
321
322 Self::insert_storage_slots(&tx, account_id, account.storage().slots().iter())?;
323
324 Self::insert_assets(&tx, account_id, account.vault().assets())?;
325 Self::insert_account_header(&tx, &account.into(), account.seed(), None)?;
326
327 Self::insert_address(&tx, initial_address, account.id())?;
328
329 tx.commit().into_store_error()?;
330
331 let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
332 smt_forest.insert_and_register_account_state(
333 account.id(),
334 account.vault(),
335 account.storage(),
336 )?;
337
338 Ok(())
339 }
340
341 pub(crate) fn update_account(
342 conn: &mut Connection,
343 smt_forest: &Arc<RwLock<AccountSmtForest>>,
344 new_account_state: &Account,
345 ) -> Result<(), StoreError> {
346 const QUERY: &str = "SELECT id FROM latest_account_headers WHERE id = ?";
347 if conn
348 .prepare(QUERY)
349 .into_store_error()?
350 .query_map(params![new_account_state.id().to_hex()], |row| row.get(0))
351 .into_store_error()?
352 .map(|result| {
353 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
354 |id: String| {
355 AccountId::from_hex(&id).map_err(|err| {
356 StoreError::AccountError(
357 AccountError::FinalAccountHeaderIdParsingFailed(err),
358 )
359 })
360 },
361 )
362 })
363 .next()
364 .is_none()
365 {
366 return Err(StoreError::AccountDataNotFound(new_account_state.id()));
367 }
368
369 let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
370 let tx = conn.transaction().into_store_error()?;
371 Self::update_account_state(&tx, &mut smt_forest, new_account_state)?;
372 tx.commit().into_store_error()
373 }
374
375 pub fn upsert_foreign_account_code(
376 conn: &mut Connection,
377 account_id: AccountId,
378 code: &AccountCode,
379 ) -> Result<(), StoreError> {
380 let tx = conn.transaction().into_store_error()?;
381
382 Self::insert_account_code(&tx, code)?;
383
384 const QUERY: &str =
385 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
386
387 tx.execute(QUERY, params![account_id.to_hex(), code.commitment().to_string()])
388 .into_store_error()?;
389
390 Self::insert_account_code(&tx, code)?;
391 tx.commit().into_store_error()
392 }
393
394 pub(crate) fn insert_address(
395 tx: &Transaction<'_>,
396 address: &Address,
397 account_id: AccountId,
398 ) -> Result<(), StoreError> {
399 let derived_note_tag = address.to_note_tag();
400 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
401
402 add_note_tag_tx(tx, ¬e_tag_record)?;
403 Self::insert_address_internal(tx, address, account_id)?;
404
405 Ok(())
406 }
407
408 pub(crate) fn remove_address(
409 conn: &mut Connection,
410 address: &Address,
411 account_id: AccountId,
412 ) -> Result<(), StoreError> {
413 let derived_note_tag = address.to_note_tag();
414 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
415
416 let tx = conn.transaction().into_store_error()?;
417 remove_note_tag_tx(&tx, note_tag_record)?;
418 Self::remove_address_internal(&tx, address)?;
419
420 tx.commit().into_store_error()
421 }
422
423 pub(crate) fn insert_account_code(
425 tx: &Transaction<'_>,
426 account_code: &AccountCode,
427 ) -> Result<(), StoreError> {
428 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
429 tx.execute(QUERY, params![account_code.commitment().to_hex(), account_code.to_bytes()])
430 .into_store_error()?;
431 Ok(())
432 }
433
434 pub(crate) fn apply_account_delta(
438 tx: &Transaction<'_>,
439 smt_forest: &mut AccountSmtForest,
440 init_account_state: &AccountHeader,
441 final_account_state: &AccountHeader,
442 updated_fungible_assets: BTreeMap<AssetVaultKey, FungibleAsset>,
443 old_map_roots: &BTreeMap<StorageSlotName, Word>,
444 delta: &AccountDelta,
445 ) -> Result<(), StoreError> {
446 let account_id = final_account_state.id();
447
448 Self::insert_account_header(tx, final_account_state, None, Some(init_account_state))?;
450
451 Self::apply_account_vault_delta(
452 tx,
453 smt_forest,
454 account_id,
455 init_account_state,
456 final_account_state,
457 updated_fungible_assets,
458 delta,
459 )?;
460
461 let mut final_roots = smt_forest
466 .get_roots(&init_account_state.id())
467 .cloned()
468 .ok_or(StoreError::AccountDataNotFound(init_account_state.id()))?;
469
470 if let Some(vault_root) = final_roots.first_mut() {
472 *vault_root = final_account_state.vault_root();
473 }
474
475 let default_map_root = StorageMap::default().root();
476 let updated_storage_slots =
477 Self::apply_account_storage_delta(smt_forest, old_map_roots, delta)?;
478
479 for (slot_name, (new_root, slot_type)) in &updated_storage_slots {
481 if *slot_type == StorageSlotType::Map {
482 let old_root = old_map_roots.get(slot_name).copied().unwrap_or(default_map_root);
483 if let Some(root) = final_roots.iter_mut().find(|r| **r == old_root) {
484 *root = *new_root;
485 } else {
486 final_roots.push(*new_root);
488 }
489 }
490 }
491
492 Self::write_storage_delta(
493 tx,
494 account_id,
495 final_account_state.nonce().as_canonical_u64(),
496 &updated_storage_slots,
497 delta,
498 )?;
499
500 smt_forest.stage_roots(final_account_state.id(), final_roots);
501
502 Ok(())
503 }
504
505 pub(crate) fn undo_account_state(
507 tx: &Transaction<'_>,
508 smt_forest: &mut AccountSmtForest,
509 discarded_states: &[(AccountId, Word)],
510 ) -> Result<(), StoreError> {
511 if discarded_states.is_empty() {
512 return Ok(());
513 }
514
515 let commitment_params = Rc::new(
516 discarded_states
517 .iter()
518 .map(|(_, commitment)| Value::from(commitment.to_hex()))
519 .collect::<Vec<_>>(),
520 );
521
522 let mut id_nonce_pairs: Vec<(String, u64)> = Vec::new();
525 for query in [
526 "SELECT id, nonce FROM latest_account_headers WHERE account_commitment IN rarray(?)",
527 "SELECT id, nonce FROM historical_account_headers WHERE account_commitment IN rarray(?)",
528 ] {
529 id_nonce_pairs.extend(
530 tx.prepare(query)
531 .into_store_error()?
532 .query_map(params![commitment_params.clone()], |row| {
533 let id: String = row.get(0)?;
534 let nonce: u64 = column_value_as_u64(row, 1)?;
535 Ok((id, nonce))
536 })
537 .into_store_error()?
538 .filter_map(Result::ok),
539 );
540 }
541
542 let mut nonces_by_account: BTreeMap<String, Vec<u64>> = BTreeMap::new();
547 for (id, nonce) in &id_nonce_pairs {
548 nonces_by_account.entry(id.clone()).or_default().push(*nonce);
549 }
550 for nonces in nonces_by_account.values_mut() {
551 nonces.sort_unstable();
552 nonces.dedup();
553 nonces.reverse();
554 }
555
556 for (account_id_hex, nonces) in &nonces_by_account {
558 Self::undo_account_nonces(tx, account_id_hex, nonces)?;
559 }
560
561 for (account_id, _) in discarded_states {
563 smt_forest.discard_roots(*account_id);
564 }
565
566 Ok(())
567 }
568
569 fn undo_account_nonces(
572 tx: &Transaction<'_>,
573 account_id_hex: &str,
574 nonces: &[u64],
575 ) -> Result<(), StoreError> {
576 for &nonce in nonces {
578 let nonce_val = u64_to_value(nonce);
579 Self::restore_old_values_for_nonce(tx, account_id_hex, &nonce_val)?;
580 }
581
582 let min_nonce = *nonces.last().unwrap();
584 let min_nonce_val = u64_to_value(min_nonce);
585
586 let old_header_exists: bool = tx
587 .query_row(
588 "SELECT COUNT(*) FROM historical_account_headers \
589 WHERE id = ? AND replaced_at_nonce = ?",
590 params![account_id_hex, &min_nonce_val],
591 |row| row.get::<_, i64>(0),
592 )
593 .into_store_error()?
594 > 0;
595
596 if old_header_exists {
597 tx.execute(
598 "INSERT OR REPLACE INTO latest_account_headers \
599 (id, account_commitment, code_commitment, storage_commitment, \
600 vault_root, nonce, account_seed, locked) \
601 SELECT id, account_commitment, code_commitment, storage_commitment, \
602 vault_root, nonce, account_seed, locked \
603 FROM historical_account_headers \
604 WHERE id = ? AND replaced_at_nonce = ?",
605 params![account_id_hex, &min_nonce_val],
606 )
607 .into_store_error()?;
608 } else {
609 for table in [
611 "DELETE FROM latest_account_headers WHERE id = ?",
612 "DELETE FROM latest_account_storage WHERE account_id = ?",
613 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
614 "DELETE FROM latest_account_assets WHERE account_id = ?",
615 ] {
616 tx.execute(table, params![account_id_hex]).into_store_error()?;
617 }
618 }
619
620 let nonce_params = Rc::new(nonces.iter().map(|n| u64_to_value(*n)).collect::<Vec<_>>());
622 for table in [
623 "historical_account_storage",
624 "historical_storage_map_entries",
625 "historical_account_assets",
626 ] {
627 tx.execute(
628 &format!(
629 "DELETE FROM {table} WHERE account_id = ? AND replaced_at_nonce IN rarray(?)"
630 ),
631 params![account_id_hex, nonce_params.clone()],
632 )
633 .into_store_error()?;
634 }
635 tx.execute(
636 "DELETE FROM historical_account_headers \
637 WHERE id = ? AND replaced_at_nonce IN rarray(?)",
638 params![account_id_hex, nonce_params],
639 )
640 .into_store_error()?;
641
642 Ok(())
643 }
644
645 fn restore_old_values_for_nonce(
648 tx: &Transaction<'_>,
649 account_id_hex: &str,
650 nonce_val: &rusqlite::types::Value,
651 ) -> Result<(), StoreError> {
652 tx.execute(
654 "INSERT OR REPLACE INTO latest_account_storage \
655 (account_id, slot_name, slot_value, slot_type) \
656 SELECT account_id, slot_name, old_slot_value, slot_type \
657 FROM historical_account_storage \
658 WHERE account_id = ? AND replaced_at_nonce = ? AND old_slot_value IS NOT NULL",
659 params![account_id_hex, nonce_val],
660 )
661 .into_store_error()?;
662
663 tx.execute(
665 "DELETE FROM latest_account_storage \
666 WHERE account_id = ?1 AND slot_name IN (\
667 SELECT slot_name FROM historical_account_storage \
668 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_slot_value IS NULL\
669 )",
670 params![account_id_hex, nonce_val],
671 )
672 .into_store_error()?;
673
674 tx.execute(
676 "INSERT OR REPLACE INTO latest_storage_map_entries \
677 (account_id, slot_name, key, value) \
678 SELECT account_id, slot_name, key, old_value \
679 FROM historical_storage_map_entries \
680 WHERE account_id = ? AND replaced_at_nonce = ? AND old_value IS NOT NULL",
681 params![account_id_hex, nonce_val],
682 )
683 .into_store_error()?;
684
685 tx.execute(
687 "DELETE FROM latest_storage_map_entries \
688 WHERE account_id = ?1 AND EXISTS (\
689 SELECT 1 FROM historical_storage_map_entries h \
690 WHERE h.account_id = latest_storage_map_entries.account_id \
691 AND h.slot_name = latest_storage_map_entries.slot_name \
692 AND h.key = latest_storage_map_entries.key \
693 AND h.replaced_at_nonce = ?2 AND h.old_value IS NULL\
694 )",
695 params![account_id_hex, nonce_val],
696 )
697 .into_store_error()?;
698
699 tx.execute(
701 "INSERT OR REPLACE INTO latest_account_assets \
702 (account_id, vault_key, asset) \
703 SELECT account_id, vault_key, old_asset \
704 FROM historical_account_assets \
705 WHERE account_id = ? AND replaced_at_nonce = ? AND old_asset IS NOT NULL",
706 params![account_id_hex, nonce_val],
707 )
708 .into_store_error()?;
709
710 tx.execute(
712 "DELETE FROM latest_account_assets \
713 WHERE account_id = ?1 AND vault_key IN (\
714 SELECT vault_key FROM historical_account_assets \
715 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_asset IS NULL\
716 )",
717 params![account_id_hex, nonce_val],
718 )
719 .into_store_error()?;
720
721 Ok(())
722 }
723
724 pub(crate) fn update_account_state(
729 tx: &Transaction<'_>,
730 smt_forest: &mut AccountSmtForest,
731 new_account_state: &Account,
732 ) -> Result<(), StoreError> {
733 let account_id = new_account_state.id();
734 let account_id_hex = account_id.to_hex();
735 let nonce_val = u64_to_value(new_account_state.nonce().as_canonical_u64());
736
737 smt_forest.insert_and_register_account_state(
739 account_id,
740 new_account_state.vault(),
741 new_account_state.storage(),
742 )?;
743
744 let old_header = query_latest_account_headers(tx, "id = ?", params![account_id.to_hex()])?
746 .into_iter()
747 .next()
748 .map(|(header, _)| header);
749
750 tx.execute(
752 "INSERT OR REPLACE INTO historical_account_storage \
753 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
754 SELECT account_id, ?, slot_name, slot_value, slot_type \
755 FROM latest_account_storage WHERE account_id = ?",
756 params![&nonce_val, &account_id_hex],
757 )
758 .into_store_error()?;
759 tx.execute(
760 "INSERT OR REPLACE INTO historical_storage_map_entries \
761 (account_id, replaced_at_nonce, slot_name, key, old_value) \
762 SELECT account_id, ?, slot_name, key, value \
763 FROM latest_storage_map_entries WHERE account_id = ?",
764 params![&nonce_val, &account_id_hex],
765 )
766 .into_store_error()?;
767 tx.execute(
768 "INSERT OR REPLACE INTO historical_account_assets \
769 (account_id, replaced_at_nonce, vault_key, old_asset) \
770 SELECT account_id, ?, vault_key, asset \
771 FROM latest_account_assets WHERE account_id = ?",
772 params![&nonce_val, &account_id_hex],
773 )
774 .into_store_error()?;
775
776 tx.execute(
778 "DELETE FROM latest_account_storage WHERE account_id = ?",
779 params![&account_id_hex],
780 )
781 .into_store_error()?;
782 tx.execute(
783 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
784 params![&account_id_hex],
785 )
786 .into_store_error()?;
787 tx.execute(
788 "DELETE FROM latest_account_assets WHERE account_id = ?",
789 params![&account_id_hex],
790 )
791 .into_store_error()?;
792
793 Self::insert_storage_slots(tx, account_id, new_account_state.storage().slots().iter())?;
795 Self::insert_assets(tx, account_id, new_account_state.vault().assets())?;
796
797 tx.execute(
800 "INSERT OR IGNORE INTO historical_account_storage \
801 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
802 SELECT account_id, ?, slot_name, NULL, slot_type \
803 FROM latest_account_storage WHERE account_id = ?",
804 params![&nonce_val, &account_id_hex],
805 )
806 .into_store_error()?;
807 tx.execute(
808 "INSERT OR IGNORE INTO historical_storage_map_entries \
809 (account_id, replaced_at_nonce, slot_name, key, old_value) \
810 SELECT account_id, ?, slot_name, key, NULL \
811 FROM latest_storage_map_entries WHERE account_id = ?",
812 params![&nonce_val, &account_id_hex],
813 )
814 .into_store_error()?;
815 tx.execute(
816 "INSERT OR IGNORE INTO historical_account_assets \
817 (account_id, replaced_at_nonce, vault_key, old_asset) \
818 SELECT account_id, ?, vault_key, NULL \
819 FROM latest_account_assets WHERE account_id = ?",
820 params![&nonce_val, &account_id_hex],
821 )
822 .into_store_error()?;
823
824 Self::insert_account_header(tx, &new_account_state.into(), None, old_header.as_ref())?;
826
827 Ok(())
828 }
829
830 pub(crate) fn apply_sync_account_delta(
832 tx: &Transaction<'_>,
833 smt_forest: &mut AccountSmtForest,
834 new_header: &AccountHeader,
835 delta: &AccountDelta,
836 ) -> Result<(), StoreError> {
837 let account_id = new_header.id();
838
839 let init_header = query_latest_account_headers(tx, "id = ?", params![account_id.to_hex()])?
841 .into_iter()
842 .next()
843 .map(|(header, _)| header)
844 .ok_or(StoreError::AccountDataNotFound(account_id))?;
845
846 let updated_fungible_assets =
849 Self::get_account_fungible_assets_for_delta(tx, account_id, delta)?;
850
851 let old_map_roots = Self::get_storage_map_roots_for_delta(tx, account_id, delta)?;
853
854 Self::apply_account_delta(
855 tx,
856 smt_forest,
857 &init_header,
858 new_header,
859 updated_fungible_assets,
860 &old_map_roots,
861 delta,
862 )
863 }
864
865 pub(crate) fn lock_account_on_unexpected_commitment(
868 tx: &Transaction<'_>,
869 account_id: &AccountId,
870 mismatched_digest: &Word,
871 ) -> Result<(), StoreError> {
872 const LOCK_CONDITION: &str = "WHERE id = :account_id AND NOT EXISTS (SELECT 1 FROM historical_account_headers WHERE id = :account_id AND account_commitment = :digest)";
876 let account_id_hex = account_id.to_hex();
877 let digest_str = mismatched_digest.to_string();
878 let params = named_params! {
879 ":account_id": account_id_hex,
880 ":digest": digest_str
881 };
882
883 let query = format!("UPDATE latest_account_headers SET locked = true {LOCK_CONDITION}");
884 tx.execute(&query, params).into_store_error()?;
885
886 let query = format!("UPDATE historical_account_headers SET locked = true {LOCK_CONDITION}");
888 tx.execute(&query, params).into_store_error()?;
889
890 Ok(())
891 }
892
893 fn insert_account_header(
901 tx: &Transaction<'_>,
902 new_header: &AccountHeader,
903 account_seed: Option<Word>,
904 old_header: Option<&AccountHeader>,
905 ) -> Result<(), StoreError> {
906 if let Some(old) = old_header {
908 let old_id = old.id().to_hex();
909 let old_code_commitment = old.code_commitment().to_string();
910 let old_storage_commitment = old.storage_commitment().to_string();
911 let old_vault_root = old.vault_root().to_string();
912 let old_nonce = u64_to_value(old.nonce().as_canonical_u64());
913 let old_commitment = old.to_commitment().to_string();
914 let replaced_at_nonce = u64_to_value(new_header.nonce().as_canonical_u64());
915
916 let (old_seed, old_locked): (Option<Vec<u8>>, bool) = tx
918 .query_row(
919 "SELECT account_seed, locked FROM latest_account_headers WHERE id = ?",
920 params![&old_id],
921 |row| Ok((row.get(0)?, row.get(1)?)),
922 )
923 .optional()
924 .into_store_error()?
925 .unwrap_or((None, false));
926
927 const HISTORICAL_QUERY: &str = insert_sql!(
928 historical_account_headers {
929 id,
930 code_commitment,
931 storage_commitment,
932 vault_root,
933 nonce,
934 account_seed,
935 account_commitment,
936 locked,
937 replaced_at_nonce
938 } | REPLACE
939 );
940
941 tx.execute(
942 HISTORICAL_QUERY,
943 params![
944 old_id,
945 old_code_commitment,
946 old_storage_commitment,
947 old_vault_root,
948 old_nonce,
949 old_seed,
950 old_commitment,
951 old_locked,
952 replaced_at_nonce,
953 ],
954 )
955 .into_store_error()?;
956 }
957
958 let id = new_header.id().to_hex();
960 let code_commitment = new_header.code_commitment().to_string();
961 let storage_commitment = new_header.storage_commitment().to_string();
962 let vault_root = new_header.vault_root().to_string();
963 let nonce = u64_to_value(new_header.nonce().as_canonical_u64());
964 let commitment = new_header.to_commitment().to_string();
965 let account_seed = account_seed.map(|seed| seed.to_bytes());
966
967 const LATEST_QUERY: &str = insert_sql!(
968 latest_account_headers {
969 id,
970 code_commitment,
971 storage_commitment,
972 vault_root,
973 nonce,
974 account_seed,
975 account_commitment,
976 locked
977 } | REPLACE
978 );
979
980 tx.execute(
981 LATEST_QUERY,
982 params![
983 id,
984 code_commitment,
985 storage_commitment,
986 vault_root,
987 nonce,
988 account_seed,
989 commitment,
990 false,
991 ],
992 )
993 .into_store_error()?;
994
995 Ok(())
996 }
997
998 fn insert_address_internal(
999 tx: &Transaction<'_>,
1000 address: &Address,
1001 account_id: AccountId,
1002 ) -> Result<(), StoreError> {
1003 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
1004 let serialized_address = address.to_bytes();
1005 tx.execute(QUERY, params![serialized_address, account_id.to_hex(),])
1006 .into_store_error()?;
1007
1008 Ok(())
1009 }
1010
1011 pub fn prune_account_history(
1017 conn: &mut Connection,
1018 account_id: AccountId,
1019 up_to_nonce: Felt,
1020 ) -> Result<usize, StoreError> {
1021 let tx = conn.transaction().into_store_error()?;
1022 let account_id_hex = account_id.to_hex();
1023 let boundary_val = u64_to_value(up_to_nonce.as_canonical_u64());
1024 let mut total_deleted: usize = 0;
1025
1026 let candidate_code_commitments: Vec<String> = {
1028 let mut stmt = tx
1029 .prepare(
1030 "SELECT DISTINCT code_commitment FROM historical_account_headers \
1031 WHERE id = ? AND replaced_at_nonce <= ?",
1032 )
1033 .into_store_error()?;
1034 let rows = stmt
1035 .query_map(params![&account_id_hex, &boundary_val], |row| row.get(0))
1036 .into_store_error()?;
1037 rows.collect::<Result<Vec<String>, _>>().into_store_error()?
1038 };
1039
1040 total_deleted += tx
1042 .execute(
1043 "DELETE FROM historical_account_headers \
1044 WHERE id = ? AND replaced_at_nonce <= ?",
1045 params![&account_id_hex, &boundary_val],
1046 )
1047 .into_store_error()?;
1048
1049 total_deleted += tx
1050 .execute(
1051 "DELETE FROM historical_account_storage \
1052 WHERE account_id = ? AND replaced_at_nonce <= ?",
1053 params![&account_id_hex, &boundary_val],
1054 )
1055 .into_store_error()?;
1056
1057 total_deleted += tx
1058 .execute(
1059 "DELETE FROM historical_storage_map_entries \
1060 WHERE account_id = ? AND replaced_at_nonce <= ?",
1061 params![&account_id_hex, &boundary_val],
1062 )
1063 .into_store_error()?;
1064
1065 total_deleted += tx
1066 .execute(
1067 "DELETE FROM historical_account_assets \
1068 WHERE account_id = ? AND replaced_at_nonce <= ?",
1069 params![&account_id_hex, &boundary_val],
1070 )
1071 .into_store_error()?;
1072
1073 for commitment in &candidate_code_commitments {
1076 let still_referenced: bool = tx
1077 .query_row(
1078 "SELECT EXISTS(
1079 SELECT 1 FROM latest_account_headers WHERE code_commitment = ?1
1080 UNION ALL
1081 SELECT 1 FROM historical_account_headers WHERE code_commitment = ?1
1082 UNION ALL
1083 SELECT 1 FROM foreign_account_code WHERE code_commitment = ?1
1084 )",
1085 params![commitment],
1086 |row| row.get(0),
1087 )
1088 .into_store_error()?;
1089
1090 if !still_referenced {
1091 total_deleted += tx
1092 .execute("DELETE FROM account_code WHERE commitment = ?", params![commitment])
1093 .into_store_error()?;
1094 }
1095 }
1096
1097 tx.commit().into_store_error()?;
1098 Ok(total_deleted)
1099 }
1100
1101 fn remove_address_internal(tx: &Transaction<'_>, address: &Address) -> Result<(), StoreError> {
1102 let serialized_address = address.to_bytes();
1103
1104 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
1105 tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
1106
1107 Ok(())
1108 }
1109}