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