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 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::{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::transaction::with_forest_snapshot;
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 Ok(query_latest_account_headers(conn, "1=1 ORDER BY id", params![])?
78 .into_iter()
79 .map(|(header, status, _)| (header, status))
80 .collect())
81 }
82
83 pub(crate) fn get_account_header(
84 conn: &Connection,
85 account_id: AccountId,
86 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
87 Ok(query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?
88 .pop()
89 .map(|(header, status, _)| (header, status)))
90 }
91
92 pub(crate) fn get_account_header_by_commitment(
93 conn: &mut Connection,
94 account_commitment: Word,
95 ) -> Result<Option<AccountHeader>, StoreError> {
96 let account_commitment_str: String = account_commitment.to_string();
97 Ok(query_historical_account_headers(
98 conn,
99 "account_commitment = ?",
100 params![account_commitment_str],
101 )?
102 .pop()
103 .map(|(header, _)| header))
104 }
105
106 pub(crate) fn get_account(
108 conn: &mut Connection,
109 account_id: AccountId,
110 ) -> Result<Option<AccountRecord>, StoreError> {
111 let Some((header, status, client_account_type)) =
112 query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?.pop()
113 else {
114 return Ok(None);
115 };
116
117 let assets = query_vault_assets(conn, account_id)?;
118 let vault = AssetVault::new(&assets)?;
119
120 let slots = query_storage_slots(conn, account_id, &AccountStorageFilter::All)?
121 .into_values()
122 .collect();
123
124 let storage = AccountStorage::new(slots)?;
125
126 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
127 return Ok(None);
128 };
129
130 let account = Account::new_unchecked(
131 header.id(),
132 vault,
133 storage,
134 account_code,
135 header.nonce(),
136 status.seed().copied(),
137 );
138
139 let account_data = AccountRecordData::Full(account);
140 Ok(Some(AccountRecord::new(account_data, status, client_account_type)))
141 }
142
143 pub(crate) fn get_minimal_partial_account(
145 conn: &mut Connection,
146 account_id: AccountId,
147 ) -> Result<Option<AccountRecord>, StoreError> {
148 let Some((header, status, client_account_type)) =
149 query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?.pop()
150 else {
151 return Ok(None);
152 };
153
154 let partial_vault = PartialVault::new(header.vault_root());
156
157 let mut storage_header = Vec::new();
159 let mut maps = vec![];
160
161 let storage_values = query_storage_values(conn, account_id)?;
162
163 for (slot_name, (slot_type, value)) in storage_values {
167 storage_header.push(StorageSlotHeader::new(slot_name.clone(), slot_type, value));
168 if slot_type == StorageSlotType::Map {
169 maps.push(PartialStorageMap::new(value));
170 }
171 }
172 storage_header.sort_by_key(StorageSlotHeader::id);
173 let storage_header =
174 AccountStorageHeader::new(storage_header).map_err(StoreError::AccountError)?;
175 let partial_storage =
176 PartialStorage::new(storage_header, maps).map_err(StoreError::AccountError)?;
177
178 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
179 return Ok(None);
180 };
181
182 let partial_account = PartialAccount::new(
183 header.id(),
184 header.nonce(),
185 account_code,
186 partial_storage,
187 partial_vault,
188 status.seed().copied(),
189 )?;
190 let account_record_data = AccountRecordData::Partial(partial_account);
191 Ok(Some(AccountRecord::new(account_record_data, status, client_account_type)))
192 }
193
194 pub fn get_foreign_account_code(
195 conn: &mut Connection,
196 account_ids: Vec<AccountId>,
197 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
198 let params: Vec<Value> =
199 account_ids.into_iter().map(|id| Value::from(id.to_hex())).collect();
200 const QUERY: &str = "
201 SELECT account_id, code
202 FROM foreign_account_code JOIN account_code ON foreign_account_code.code_commitment = account_code.commitment
203 WHERE account_id IN rarray(?)";
204
205 conn.prepare_cached(QUERY)
206 .into_store_error()?
207 .query_map([Rc::new(params)], |row| Ok((row.get(0)?, row.get(1)?)))
208 .expect("no binding parameters used in query")
209 .map(|result| {
210 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
211 |(id, code): (String, Vec<u8>)| {
212 Ok((
213 AccountId::from_hex(&id).map_err(|err| {
214 StoreError::AccountError(
215 AccountError::FinalAccountHeaderIdParsingFailed(err),
216 )
217 })?,
218 AccountCode::read_from_bytes(&code)
219 .map_err(StoreError::DataDeserializationError)?,
220 ))
221 },
222 )
223 })
224 .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
225 }
226
227 pub fn get_account_vault(
229 conn: &Connection,
230 account_id: AccountId,
231 ) -> Result<AssetVault, StoreError> {
232 let assets = query_vault_assets(conn, account_id)?;
233 Ok(AssetVault::new(&assets)?)
234 }
235
236 pub fn get_account_storage(
238 conn: &Connection,
239 account_id: AccountId,
240 filter: &AccountStorageFilter,
241 ) -> Result<AccountStorage, StoreError> {
242 let slots = query_storage_slots(conn, account_id, filter)?.into_values().collect();
243 Ok(AccountStorage::new(slots)?)
244 }
245
246 pub(crate) fn get_account_asset(
249 conn: &mut Connection,
250 smt_forest: &Arc<RwLock<AccountSmtForest>>,
251 account_id: AccountId,
252 vault_key: AssetVaultKey,
253 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
254 let smt_forest = smt_forest
256 .read()
257 .map_err(|_| StoreError::DatabaseError("smt_forest read lock poisoned".to_string()))?;
258 let header = Self::get_account_header(conn, account_id)?
259 .ok_or(StoreError::AccountDataNotFound(account_id))?
260 .0;
261
262 match smt_forest.get_asset_and_witness(header.vault_root(), vault_key) {
263 Ok((asset, witness)) => Ok(Some((asset, witness))),
264 Err(StoreError::MerkleStoreError(MerkleError::UntrackedKey(_))) => Ok(None),
265 Err(err) => Err(err),
266 }
267 }
268
269 pub(crate) fn get_account_map_item(
272 conn: &mut Connection,
273 smt_forest: &Arc<RwLock<AccountSmtForest>>,
274 account_id: AccountId,
275 slot_name: StorageSlotName,
276 key: StorageMapKey,
277 ) -> Result<(Word, StorageMapWitness), StoreError> {
278 let smt_forest = smt_forest
280 .read()
281 .map_err(|_| StoreError::DatabaseError("smt_forest read lock poisoned".to_string()))?;
282 let header = Self::get_account_header(conn, account_id)?
283 .ok_or(StoreError::AccountDataNotFound(account_id))?
284 .0;
285
286 let mut storage_values = query_storage_values(conn, account_id)?;
287 let (slot_type, map_root) = storage_values
288 .remove(&slot_name)
289 .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))?;
290 if slot_type != StorageSlotType::Map {
291 return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name)));
292 }
293
294 let witness = smt_forest.get_storage_map_item_witness(map_root, key)?;
295 let item = witness.get(key).unwrap_or(miden_client::EMPTY_WORD);
296
297 Ok((item, witness))
298 }
299
300 pub(crate) fn get_account_addresses(
301 conn: &mut Connection,
302 account_id: AccountId,
303 ) -> Result<Vec<Address>, StoreError> {
304 query_account_addresses(conn, account_id)
305 }
306
307 pub(crate) fn get_account_code_by_id(
309 conn: &mut Connection,
310 account_id: AccountId,
311 ) -> Result<Option<AccountCode>, StoreError> {
312 let Some((header, ..)) =
313 query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?
314 .into_iter()
315 .next()
316 else {
317 return Ok(None);
318 };
319
320 query_account_code(conn, header.code_commitment())
321 }
322
323 pub(crate) fn insert_account(
327 conn: &mut Connection,
328 smt_forest: &Arc<RwLock<AccountSmtForest>>,
329 account: &Account,
330 initial_address: &Address,
331 client_account_type: ClientAccountType,
332 ) -> Result<(), StoreError> {
333 with_forest_snapshot(conn, smt_forest, |tx, smt_forest| {
334 Self::insert_account_code(tx, account.code())?;
335
336 let account_id = account.id();
337 Self::insert_storage_slots(tx, account_id, account.storage().slots().iter())?;
338 Self::insert_assets(tx, account_id, account.vault().assets())?;
339 let watched = matches!(client_account_type, ClientAccountType::Watched);
340 Self::insert_new_account_header(tx, &account.into(), account.seed(), watched)?;
341 Self::insert_address(tx, initial_address, account.id())?;
342
343 smt_forest.insert_and_register_account_state(
344 account.id(),
345 account.vault(),
346 account.storage(),
347 )?;
348 Ok(())
349 })
350 }
351
352 pub(crate) fn update_account(
353 conn: &mut Connection,
354 smt_forest: &Arc<RwLock<AccountSmtForest>>,
355 new_account_state: &Account,
356 ) -> Result<(), StoreError> {
357 const QUERY: &str = "SELECT id FROM latest_account_headers WHERE id = ?";
358 if conn
359 .prepare(QUERY)
360 .into_store_error()?
361 .query_map(params![new_account_state.id().to_hex()], |row| row.get(0))
362 .into_store_error()?
363 .map(|result| {
364 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
365 |id: String| {
366 AccountId::from_hex(&id).map_err(|err| {
367 StoreError::AccountError(
368 AccountError::FinalAccountHeaderIdParsingFailed(err),
369 )
370 })
371 },
372 )
373 })
374 .next()
375 .is_none()
376 {
377 return Err(StoreError::AccountDataNotFound(new_account_state.id()));
378 }
379
380 with_forest_snapshot(conn, smt_forest, |tx, smt_forest| {
381 Self::update_account_state(tx, smt_forest, new_account_state)
382 })
383 }
384
385 pub fn upsert_foreign_account_code(
386 conn: &mut Connection,
387 account_id: AccountId,
388 code: &AccountCode,
389 ) -> Result<(), StoreError> {
390 let tx = conn.transaction().into_store_error()?;
391
392 Self::insert_account_code(&tx, code)?;
393
394 const QUERY: &str =
395 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
396
397 tx.execute(QUERY, params![account_id.to_hex(), code.commitment().to_string()])
398 .into_store_error()?;
399
400 Self::insert_account_code(&tx, code)?;
401 tx.commit().into_store_error()
402 }
403
404 pub(crate) fn insert_address(
405 tx: &Transaction<'_>,
406 address: &Address,
407 account_id: AccountId,
408 ) -> Result<(), StoreError> {
409 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
410 let serialized_address = address.to_bytes();
411 tx.execute(QUERY, params![serialized_address, account_id.to_hex(),])
412 .into_store_error()?;
413
414 Ok(())
415 }
416
417 pub(crate) fn remove_address(
418 conn: &mut Connection,
419 address: &Address,
420 ) -> Result<(), StoreError> {
421 let tx = conn.transaction().into_store_error()?;
422 let serialized_address = address.to_bytes();
423 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
424 tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
425
426 tx.commit().into_store_error()
427 }
428
429 pub(crate) fn insert_account_code(
431 tx: &Transaction<'_>,
432 account_code: &AccountCode,
433 ) -> Result<(), StoreError> {
434 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
435 tx.execute(QUERY, params![account_code.commitment().to_hex(), account_code.to_bytes()])
436 .into_store_error()?;
437 Ok(())
438 }
439
440 pub(crate) fn apply_account_delta(
444 tx: &Transaction<'_>,
445 smt_forest: &mut AccountSmtForest,
446 init_account_state: &AccountHeader,
447 final_account_state: &AccountHeader,
448 updated_fungible_assets: BTreeMap<AssetVaultKey, FungibleAsset>,
449 old_map_roots: &BTreeMap<StorageSlotName, Word>,
450 delta: &AccountDelta,
451 ) -> Result<(), StoreError> {
452 let account_id = final_account_state.id();
453
454 Self::replace_account_header(tx, final_account_state, init_account_state)?;
456
457 Self::apply_account_vault_delta(
458 tx,
459 smt_forest,
460 account_id,
461 init_account_state,
462 final_account_state,
463 updated_fungible_assets,
464 delta,
465 )?;
466
467 let mut final_roots = smt_forest
472 .get_roots(&init_account_state.id())
473 .cloned()
474 .ok_or(StoreError::AccountDataNotFound(init_account_state.id()))?;
475
476 if let Some(vault_root) = final_roots.first_mut() {
478 *vault_root = final_account_state.vault_root();
479 }
480
481 let default_map_root = StorageMap::default().root();
482 let updated_storage_slots =
483 Self::apply_account_storage_delta(smt_forest, old_map_roots, delta)?;
484
485 for (slot_name, (new_root, slot_type)) in &updated_storage_slots {
487 if *slot_type == StorageSlotType::Map {
488 let old_root = old_map_roots.get(slot_name).copied().unwrap_or(default_map_root);
489 if let Some(root) = final_roots.iter_mut().find(|r| **r == old_root) {
490 *root = *new_root;
491 } else {
492 final_roots.push(*new_root);
494 }
495 }
496 }
497
498 Self::write_storage_delta(
499 tx,
500 account_id,
501 final_account_state.nonce().as_canonical_u64(),
502 &updated_storage_slots,
503 delta,
504 )?;
505
506 smt_forest.stage_roots(final_account_state.id(), final_roots);
507
508 Ok(())
509 }
510
511 pub(crate) fn undo_account_state(
513 tx: &Transaction<'_>,
514 smt_forest: &mut AccountSmtForest,
515 discarded_states: &[(AccountId, Word)],
516 ) -> Result<(), StoreError> {
517 if discarded_states.is_empty() {
518 return Ok(());
519 }
520
521 let commitment_params = Rc::new(
522 discarded_states
523 .iter()
524 .map(|(_, commitment)| Value::from(commitment.to_hex()))
525 .collect::<Vec<_>>(),
526 );
527
528 let mut id_nonce_pairs: Vec<(String, u64)> = Vec::new();
531 for query in [
532 "SELECT id, nonce FROM latest_account_headers WHERE account_commitment IN rarray(?)",
533 "SELECT id, nonce FROM historical_account_headers WHERE account_commitment IN rarray(?)",
534 ] {
535 id_nonce_pairs.extend(
536 tx.prepare(query)
537 .into_store_error()?
538 .query_map(params![commitment_params.clone()], |row| {
539 let id: String = row.get(0)?;
540 let nonce: u64 = column_value_as_u64(row, 1)?;
541 Ok((id, nonce))
542 })
543 .into_store_error()?
544 .filter_map(Result::ok),
545 );
546 }
547
548 let mut nonces_by_account: BTreeMap<String, Vec<u64>> = BTreeMap::new();
553 for (id, nonce) in &id_nonce_pairs {
554 nonces_by_account.entry(id.clone()).or_default().push(*nonce);
555 }
556 for nonces in nonces_by_account.values_mut() {
557 nonces.sort_unstable();
558 nonces.dedup();
559 nonces.reverse();
560 }
561
562 for (account_id_hex, nonces) in &nonces_by_account {
564 Self::undo_account_nonces(tx, account_id_hex, nonces)?;
565 }
566
567 for (account_id, _) in discarded_states {
569 smt_forest.discard_roots(*account_id);
570 }
571
572 Ok(())
573 }
574
575 fn undo_account_nonces(
578 tx: &Transaction<'_>,
579 account_id_hex: &str,
580 nonces: &[u64],
581 ) -> Result<(), StoreError> {
582 for &nonce in nonces {
584 let nonce_val = u64_to_value(nonce);
585 Self::restore_old_values_for_nonce(tx, account_id_hex, &nonce_val)?;
586 }
587
588 let min_nonce = *nonces.last().unwrap();
593 let min_nonce_val = u64_to_value(min_nonce);
594
595 let old_header_exists: bool = tx
596 .query_row(
597 "SELECT COUNT(*) FROM historical_account_headers \
598 WHERE id = ? AND replaced_at_nonce = ?",
599 params![account_id_hex, &min_nonce_val],
600 |row| row.get::<_, i64>(0),
601 )
602 .into_store_error()?
603 > 0;
604
605 if old_header_exists {
606 tx.execute(
610 "INSERT OR REPLACE INTO latest_account_headers \
611 (id, account_commitment, code_commitment, storage_commitment, \
612 vault_root, nonce, account_seed, locked) \
613 SELECT id, account_commitment, code_commitment, storage_commitment, \
614 vault_root, nonce, account_seed, locked \
615 FROM historical_account_headers \
616 WHERE id = ? AND replaced_at_nonce = ?",
617 params![account_id_hex, &min_nonce_val],
618 )
619 .into_store_error()?;
620 } else {
621 for table in [
623 "DELETE FROM latest_account_headers WHERE id = ?",
624 "DELETE FROM latest_account_storage WHERE account_id = ?",
625 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
626 "DELETE FROM latest_account_assets WHERE account_id = ?",
627 ] {
628 tx.execute(table, params![account_id_hex]).into_store_error()?;
629 }
630 }
631
632 let nonce_params = Rc::new(nonces.iter().map(|n| u64_to_value(*n)).collect::<Vec<_>>());
634 for table in [
635 "historical_account_storage",
636 "historical_storage_map_entries",
637 "historical_account_assets",
638 ] {
639 tx.execute(
640 &format!(
641 "DELETE FROM {table} WHERE account_id = ? AND replaced_at_nonce IN rarray(?)"
642 ),
643 params![account_id_hex, nonce_params.clone()],
644 )
645 .into_store_error()?;
646 }
647 tx.execute(
648 "DELETE FROM historical_account_headers \
649 WHERE id = ? AND replaced_at_nonce IN rarray(?)",
650 params![account_id_hex, nonce_params],
651 )
652 .into_store_error()?;
653
654 Ok(())
655 }
656
657 fn restore_old_values_for_nonce(
660 tx: &Transaction<'_>,
661 account_id_hex: &str,
662 nonce_val: &rusqlite::types::Value,
663 ) -> Result<(), StoreError> {
664 tx.execute(
666 "INSERT OR REPLACE INTO latest_account_storage \
667 (account_id, slot_name, slot_value, slot_type) \
668 SELECT account_id, slot_name, old_slot_value, slot_type \
669 FROM historical_account_storage \
670 WHERE account_id = ? AND replaced_at_nonce = ? AND old_slot_value IS NOT NULL",
671 params![account_id_hex, nonce_val],
672 )
673 .into_store_error()?;
674
675 tx.execute(
677 "DELETE FROM latest_account_storage \
678 WHERE account_id = ?1 AND slot_name IN (\
679 SELECT slot_name FROM historical_account_storage \
680 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_slot_value IS NULL\
681 )",
682 params![account_id_hex, nonce_val],
683 )
684 .into_store_error()?;
685
686 tx.execute(
688 "INSERT OR REPLACE INTO latest_storage_map_entries \
689 (account_id, slot_name, key, value) \
690 SELECT account_id, slot_name, key, old_value \
691 FROM historical_storage_map_entries \
692 WHERE account_id = ? AND replaced_at_nonce = ? AND old_value IS NOT NULL",
693 params![account_id_hex, nonce_val],
694 )
695 .into_store_error()?;
696
697 tx.execute(
699 "DELETE FROM latest_storage_map_entries \
700 WHERE account_id = ?1 AND EXISTS (\
701 SELECT 1 FROM historical_storage_map_entries h \
702 WHERE h.account_id = latest_storage_map_entries.account_id \
703 AND h.slot_name = latest_storage_map_entries.slot_name \
704 AND h.key = latest_storage_map_entries.key \
705 AND h.replaced_at_nonce = ?2 AND h.old_value IS NULL\
706 )",
707 params![account_id_hex, nonce_val],
708 )
709 .into_store_error()?;
710
711 tx.execute(
713 "INSERT OR REPLACE INTO latest_account_assets \
714 (account_id, vault_key, asset) \
715 SELECT account_id, vault_key, old_asset \
716 FROM historical_account_assets \
717 WHERE account_id = ? AND replaced_at_nonce = ? AND old_asset IS NOT NULL",
718 params![account_id_hex, nonce_val],
719 )
720 .into_store_error()?;
721
722 tx.execute(
724 "DELETE FROM latest_account_assets \
725 WHERE account_id = ?1 AND vault_key IN (\
726 SELECT vault_key FROM historical_account_assets \
727 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_asset IS NULL\
728 )",
729 params![account_id_hex, nonce_val],
730 )
731 .into_store_error()?;
732
733 Ok(())
734 }
735
736 pub(crate) fn update_account_state(
741 tx: &Transaction<'_>,
742 smt_forest: &mut AccountSmtForest,
743 new_account_state: &Account,
744 ) -> Result<(), StoreError> {
745 let account_id = new_account_state.id();
746 let account_id_hex = account_id.to_hex();
747
748 let old_header = query_latest_account_headers(tx, "id = ?", params![&account_id_hex])?
751 .into_iter()
752 .next()
753 .map(|(header, ..)| header)
754 .ok_or(StoreError::AccountDataNotFound(account_id))?;
755
756 if new_account_state.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
757 return Err(StoreError::DatabaseError(format!(
758 "update_account_state: new nonce {} is less than old nonce {} for account {}",
759 new_account_state.nonce().as_canonical_u64(),
760 old_header.nonce().as_canonical_u64(),
761 account_id,
762 )));
763 }
764
765 let nonce_val = u64_to_value(new_account_state.nonce().as_canonical_u64());
766
767 smt_forest.insert_and_register_account_state(
769 account_id,
770 new_account_state.vault(),
771 new_account_state.storage(),
772 )?;
773
774 tx.execute(
776 "INSERT OR REPLACE INTO historical_account_storage \
777 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
778 SELECT account_id, ?, slot_name, slot_value, slot_type \
779 FROM latest_account_storage WHERE account_id = ?",
780 params![&nonce_val, &account_id_hex],
781 )
782 .into_store_error()?;
783 tx.execute(
784 "INSERT OR REPLACE INTO historical_storage_map_entries \
785 (account_id, replaced_at_nonce, slot_name, key, old_value) \
786 SELECT account_id, ?, slot_name, key, value \
787 FROM latest_storage_map_entries WHERE account_id = ?",
788 params![&nonce_val, &account_id_hex],
789 )
790 .into_store_error()?;
791 tx.execute(
792 "INSERT OR REPLACE INTO historical_account_assets \
793 (account_id, replaced_at_nonce, vault_key, old_asset) \
794 SELECT account_id, ?, vault_key, asset \
795 FROM latest_account_assets WHERE account_id = ?",
796 params![&nonce_val, &account_id_hex],
797 )
798 .into_store_error()?;
799
800 tx.execute(
802 "DELETE FROM latest_account_storage WHERE account_id = ?",
803 params![&account_id_hex],
804 )
805 .into_store_error()?;
806 tx.execute(
807 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
808 params![&account_id_hex],
809 )
810 .into_store_error()?;
811 tx.execute(
812 "DELETE FROM latest_account_assets WHERE account_id = ?",
813 params![&account_id_hex],
814 )
815 .into_store_error()?;
816
817 Self::insert_storage_slots(tx, account_id, new_account_state.storage().slots().iter())?;
819 Self::insert_assets(tx, account_id, new_account_state.vault().assets())?;
820
821 tx.execute(
824 "INSERT OR IGNORE INTO historical_account_storage \
825 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
826 SELECT account_id, ?, slot_name, NULL, slot_type \
827 FROM latest_account_storage WHERE account_id = ?",
828 params![&nonce_val, &account_id_hex],
829 )
830 .into_store_error()?;
831 tx.execute(
832 "INSERT OR IGNORE INTO historical_storage_map_entries \
833 (account_id, replaced_at_nonce, slot_name, key, old_value) \
834 SELECT account_id, ?, slot_name, key, NULL \
835 FROM latest_storage_map_entries WHERE account_id = ?",
836 params![&nonce_val, &account_id_hex],
837 )
838 .into_store_error()?;
839 tx.execute(
840 "INSERT OR IGNORE INTO historical_account_assets \
841 (account_id, replaced_at_nonce, vault_key, old_asset) \
842 SELECT account_id, ?, vault_key, NULL \
843 FROM latest_account_assets WHERE account_id = ?",
844 params![&nonce_val, &account_id_hex],
845 )
846 .into_store_error()?;
847
848 Self::replace_account_header(tx, &new_account_state.into(), &old_header)?;
850
851 Ok(())
852 }
853
854 pub(crate) fn apply_sync_account_delta(
856 tx: &Transaction<'_>,
857 smt_forest: &mut AccountSmtForest,
858 new_header: &AccountHeader,
859 delta: &AccountDelta,
860 ) -> Result<(), StoreError> {
861 let account_id = new_header.id();
862
863 let init_header = query_latest_account_headers(tx, "id = ?", params![account_id.to_hex()])?
865 .into_iter()
866 .next()
867 .map(|(header, ..)| header)
868 .ok_or(StoreError::AccountDataNotFound(account_id))?;
869
870 let updated_fungible_assets =
873 Self::get_account_fungible_assets_for_delta(tx, account_id, delta)?;
874
875 let old_map_roots = Self::get_storage_map_roots_for_delta(tx, account_id, delta)?;
877
878 Self::apply_account_delta(
879 tx,
880 smt_forest,
881 &init_header,
882 new_header,
883 updated_fungible_assets,
884 &old_map_roots,
885 delta,
886 )
887 }
888
889 pub(crate) fn lock_account_on_unexpected_commitment(
892 tx: &Transaction<'_>,
893 account_id: &AccountId,
894 mismatched_digest: &Word,
895 ) -> Result<(), StoreError> {
896 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)";
900 let account_id_hex = account_id.to_hex();
901 let digest_str = mismatched_digest.to_string();
902 let params = named_params! {
903 ":account_id": account_id_hex,
904 ":digest": digest_str
905 };
906
907 let query = format!("UPDATE latest_account_headers SET locked = true {LOCK_CONDITION}");
908 tx.execute(&query, params).into_store_error()?;
909
910 let query = format!("UPDATE historical_account_headers SET locked = true {LOCK_CONDITION}");
912 tx.execute(&query, params).into_store_error()?;
913
914 Ok(())
915 }
916
917 fn insert_new_account_header(
926 tx: &Transaction<'_>,
927 new_header: &AccountHeader,
928 account_seed: Option<Word>,
929 watched: bool,
930 ) -> Result<(), StoreError> {
931 let id = new_header.id().to_hex();
932 let code_commitment = new_header.code_commitment().to_string();
933 let storage_commitment = new_header.storage_commitment().to_string();
934 let vault_root = new_header.vault_root().to_string();
935 let nonce = u64_to_value(new_header.nonce().as_canonical_u64());
936 let commitment = new_header.to_commitment().to_string();
937 let account_seed = account_seed.map(|seed| seed.to_bytes());
938
939 const LATEST_QUERY: &str = insert_sql!(
940 latest_account_headers {
941 id,
942 code_commitment,
943 storage_commitment,
944 vault_root,
945 nonce,
946 account_seed,
947 account_commitment,
948 locked,
949 watched
950 } | REPLACE
951 );
952
953 tx.execute(
954 LATEST_QUERY,
955 params![
956 id,
957 code_commitment,
958 storage_commitment,
959 vault_root,
960 nonce,
961 account_seed,
962 commitment,
963 false,
964 watched,
965 ],
966 )
967 .into_store_error()?;
968
969 Ok(())
970 }
971
972 fn replace_account_header(
978 tx: &Transaction<'_>,
979 new_header: &AccountHeader,
980 old_header: &AccountHeader,
981 ) -> Result<(), StoreError> {
982 if new_header.id() != old_header.id() {
983 return Err(StoreError::DatabaseError(format!(
984 "replace_account_header: account id mismatch (new: {}, old: {})",
985 new_header.id(),
986 old_header.id(),
987 )));
988 }
989 if new_header.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
990 return Err(StoreError::DatabaseError(format!(
991 "replace_account_header: new nonce {} is less than old nonce {} for account {}",
992 new_header.nonce().as_canonical_u64(),
993 old_header.nonce().as_canonical_u64(),
994 new_header.id(),
995 )));
996 }
997
998 let id_hex = new_header.id().to_hex();
999
1000 let (old_seed, old_locked, old_watched): (Option<Vec<u8>>, bool, bool) = tx
1004 .query_row(
1005 "SELECT account_seed, locked, watched FROM latest_account_headers WHERE id = ?",
1006 params![&id_hex],
1007 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
1008 )
1009 .optional()
1010 .into_store_error()?
1011 .unwrap_or((None, false, false));
1012
1013 let old_id = old_header.id().to_hex();
1015 let old_code_commitment = old_header.code_commitment().to_string();
1016 let old_storage_commitment = old_header.storage_commitment().to_string();
1017 let old_vault_root = old_header.vault_root().to_string();
1018 let old_nonce = u64_to_value(old_header.nonce().as_canonical_u64());
1019 let old_commitment = old_header.to_commitment().to_string();
1020 let replaced_at_nonce = u64_to_value(new_header.nonce().as_canonical_u64());
1021
1022 const HISTORICAL_QUERY: &str = insert_sql!(
1023 historical_account_headers {
1024 id,
1025 code_commitment,
1026 storage_commitment,
1027 vault_root,
1028 nonce,
1029 account_seed,
1030 account_commitment,
1031 locked,
1032 replaced_at_nonce
1033 } | REPLACE
1034 );
1035
1036 tx.execute(
1037 HISTORICAL_QUERY,
1038 params![
1039 old_id,
1040 old_code_commitment,
1041 old_storage_commitment,
1042 old_vault_root,
1043 old_nonce,
1044 old_seed,
1045 old_commitment,
1046 old_locked,
1047 replaced_at_nonce,
1048 ],
1049 )
1050 .into_store_error()?;
1051
1052 Self::insert_new_account_header(tx, new_header, None, old_watched)
1054 }
1055
1056 pub fn prune_account_history(
1062 conn: &mut Connection,
1063 account_id: AccountId,
1064 up_to_nonce: Felt,
1065 ) -> Result<usize, StoreError> {
1066 let tx = conn.transaction().into_store_error()?;
1067 let account_id_hex = account_id.to_hex();
1068 let boundary_val = u64_to_value(up_to_nonce.as_canonical_u64());
1069 let mut total_deleted: usize = 0;
1070
1071 let candidate_code_commitments: Vec<String> = {
1073 let mut stmt = tx
1074 .prepare(
1075 "SELECT DISTINCT code_commitment FROM historical_account_headers \
1076 WHERE id = ? AND replaced_at_nonce <= ?",
1077 )
1078 .into_store_error()?;
1079 let rows = stmt
1080 .query_map(params![&account_id_hex, &boundary_val], |row| row.get(0))
1081 .into_store_error()?;
1082 rows.collect::<Result<Vec<String>, _>>().into_store_error()?
1083 };
1084
1085 total_deleted += tx
1087 .execute(
1088 "DELETE FROM historical_account_headers \
1089 WHERE id = ? AND replaced_at_nonce <= ?",
1090 params![&account_id_hex, &boundary_val],
1091 )
1092 .into_store_error()?;
1093
1094 total_deleted += tx
1095 .execute(
1096 "DELETE FROM historical_account_storage \
1097 WHERE account_id = ? AND replaced_at_nonce <= ?",
1098 params![&account_id_hex, &boundary_val],
1099 )
1100 .into_store_error()?;
1101
1102 total_deleted += tx
1103 .execute(
1104 "DELETE FROM historical_storage_map_entries \
1105 WHERE account_id = ? AND replaced_at_nonce <= ?",
1106 params![&account_id_hex, &boundary_val],
1107 )
1108 .into_store_error()?;
1109
1110 total_deleted += tx
1111 .execute(
1112 "DELETE FROM historical_account_assets \
1113 WHERE account_id = ? AND replaced_at_nonce <= ?",
1114 params![&account_id_hex, &boundary_val],
1115 )
1116 .into_store_error()?;
1117
1118 for commitment in &candidate_code_commitments {
1121 let still_referenced: bool = tx
1122 .query_row(
1123 "SELECT EXISTS(
1124 SELECT 1 FROM latest_account_headers WHERE code_commitment = ?1
1125 UNION ALL
1126 SELECT 1 FROM historical_account_headers WHERE code_commitment = ?1
1127 UNION ALL
1128 SELECT 1 FROM foreign_account_code WHERE code_commitment = ?1
1129 )",
1130 params![commitment],
1131 |row| row.get(0),
1132 )
1133 .into_store_error()?;
1134
1135 if !still_referenced {
1136 total_deleted += tx
1137 .execute("DELETE FROM account_code WHERE commitment = ?", params![commitment])
1138 .into_store_error()?;
1139 }
1140 }
1141
1142 tx.commit().into_store_error()?;
1143 Ok(total_deleted)
1144 }
1145}