1use std::collections::{BTreeMap, BTreeSet};
4use std::rc::Rc;
5use std::string::ToString;
6use std::vec::Vec;
7
8use miden_client::account::{
9 Account,
10 AccountCode,
11 AccountHeader,
12 AccountId,
13 AccountPatch,
14 AccountStorage,
15 Address,
16 PartialAccount,
17 PartialStorage,
18 PartialStorageMap,
19 StorageMapKey,
20 StorageSlotName,
21 StorageSlotType,
22};
23use miden_client::asset::{Asset, AssetVault, AssetWitness};
24use miden_client::store::{
25 AccountRecord,
26 AccountRecordData,
27 AccountStatus,
28 AccountStorageFilter,
29 AccountUpdate,
30 ClientAccountType,
31 StoreError,
32};
33use miden_client::utils::{Deserializable, Serializable};
34use miden_client::{AccountError, Felt, Word};
35use miden_protocol::account::{AccountStorageHeader, StorageMapWitness, StorageSlotHeader};
36use miden_protocol::asset::{AssetId, PartialVault};
37use miden_protocol::crypto::merkle::MerkleError;
38use rusqlite::types::Value;
39use rusqlite::{
40 Connection,
41 OptionalExtension,
42 Transaction,
43 TransactionBehavior,
44 named_params,
45 params,
46};
47
48use crate::account::helpers::{
49 query_account_addresses,
50 query_account_code,
51 query_historical_account_headers,
52 query_latest_account_headers,
53 query_storage_slots,
54 query_storage_values,
55 query_vault_assets,
56};
57use crate::forest::{ScopedAccountForest, SqliteForestBackend, allocate_forest_revision};
58use crate::sql_error::SqlResultExt;
59use crate::{SqliteStore, column_value_as_u64, insert_sql, subst, u64_to_value};
60
61impl SqliteStore {
62 pub(crate) fn get_account_ids(conn: &mut Connection) -> Result<Vec<AccountId>, StoreError> {
66 const QUERY: &str = "SELECT id FROM latest_account_headers";
67
68 conn.prepare_cached(QUERY)
69 .into_store_error()?
70 .query_map([], |row| row.get(0))
71 .expect("no binding parameters used in query")
72 .map(|result| {
73 let id: Vec<u8> = result.map_err(|e| StoreError::ParsingError(e.to_string()))?;
74 Ok(AccountId::read_from_bytes(&id).expect("account id is valid"))
75 })
76 .collect::<Result<Vec<AccountId>, StoreError>>()
77 }
78
79 pub(crate) fn get_account_headers(
80 conn: &mut Connection,
81 ) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
82 Ok(query_latest_account_headers(conn, "1=1 ORDER BY id", params![])?
83 .into_iter()
84 .map(|(header, status, _)| (header, status))
85 .collect())
86 }
87
88 pub(crate) fn get_account_header(
89 conn: &Connection,
90 account_id: AccountId,
91 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
92 Ok(query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?
93 .pop()
94 .map(|(header, status, _)| (header, status)))
95 }
96
97 pub(crate) fn get_account_header_by_commitment(
98 conn: &mut Connection,
99 account_commitment: Word,
100 ) -> Result<Option<AccountHeader>, StoreError> {
101 Ok(query_historical_account_headers(
102 conn,
103 "account_commitment = ?",
104 params![account_commitment.to_bytes()],
105 )?
106 .pop()
107 .map(|(header, _)| header))
108 }
109
110 pub(crate) fn get_account(
112 conn: &mut Connection,
113 account_id: AccountId,
114 ) -> Result<Option<AccountRecord>, StoreError> {
115 let Some((header, status, client_account_type)) =
116 query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?.pop()
117 else {
118 return Ok(None);
119 };
120
121 let assets = query_vault_assets(conn, account_id)?;
122 let vault = AssetVault::new(&assets)?;
123
124 let slots = query_storage_slots(conn, account_id, &AccountStorageFilter::All)?
125 .into_values()
126 .collect();
127
128 let storage = AccountStorage::new(slots)?;
129
130 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
131 return Ok(None);
132 };
133
134 let account = Account::new_unchecked(
135 header.id(),
136 vault,
137 storage,
138 account_code,
139 header.nonce(),
140 status.seed().copied(),
141 );
142
143 let account_data = AccountRecordData::Full(account);
144 Ok(Some(AccountRecord::new(account_data, status, client_account_type)))
145 }
146
147 pub(crate) fn get_minimal_partial_account(
149 conn: &mut Connection,
150 account_id: AccountId,
151 ) -> Result<Option<AccountRecord>, StoreError> {
152 let Some((header, status, client_account_type)) =
153 query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?.pop()
154 else {
155 return Ok(None);
156 };
157
158 let partial_vault = PartialVault::new(header.vault_root());
160
161 let mut storage_header = Vec::new();
163 let mut maps = vec![];
164
165 let storage_values = query_storage_values(conn, account_id)?;
166
167 for (slot_name, (slot_type, value)) in storage_values {
171 storage_header.push(StorageSlotHeader::new(slot_name.clone(), slot_type, value));
172 if slot_type == StorageSlotType::Map {
173 maps.push(PartialStorageMap::new(value));
174 }
175 }
176 storage_header.sort_by_key(StorageSlotHeader::id);
177 let storage_header =
178 AccountStorageHeader::new(storage_header).map_err(StoreError::AccountError)?;
179 let partial_storage =
180 PartialStorage::new(storage_header, maps).map_err(StoreError::AccountError)?;
181
182 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
183 return Ok(None);
184 };
185
186 let partial_account = PartialAccount::new(
187 header.id(),
188 header.nonce(),
189 account_code,
190 partial_storage,
191 partial_vault,
192 status.seed().copied(),
193 )?;
194 let account_record_data = AccountRecordData::Partial(partial_account);
195 Ok(Some(AccountRecord::new(account_record_data, status, client_account_type)))
196 }
197
198 pub fn get_foreign_account_code(
199 conn: &mut Connection,
200 account_ids: Vec<AccountId>,
201 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
202 let params: Vec<Value> =
203 account_ids.into_iter().map(|id| Value::Blob(id.to_bytes())).collect();
204 const QUERY: &str = "
205 SELECT account_id, code
206 FROM foreign_account_code JOIN account_code ON foreign_account_code.code_commitment = account_code.commitment
207 WHERE account_id IN rarray(?)";
208
209 conn.prepare_cached(QUERY)
210 .into_store_error()?
211 .query_map([Rc::new(params)], |row| Ok((row.get(0)?, row.get(1)?)))
212 .expect("no binding parameters used in query")
213 .map(|result| {
214 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
215 |(id, code): (Vec<u8>, Vec<u8>)| {
216 Ok((
217 AccountId::read_from_bytes(&id)
218 .map_err(StoreError::DataDeserializationError)?,
219 AccountCode::read_from_bytes(&code)
220 .map_err(StoreError::DataDeserializationError)?,
221 ))
222 },
223 )
224 })
225 .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
226 }
227
228 pub fn get_account_vault(
230 conn: &Connection,
231 account_id: AccountId,
232 ) -> Result<AssetVault, StoreError> {
233 let assets = query_vault_assets(conn, account_id)?;
234 Ok(AssetVault::new(&assets)?)
235 }
236
237 pub fn get_account_storage(
239 conn: &Connection,
240 account_id: AccountId,
241 filter: &AccountStorageFilter,
242 ) -> Result<AccountStorage, StoreError> {
243 let slots = query_storage_slots(conn, account_id, filter)?.into_values().collect();
244 Ok(AccountStorage::new(slots)?)
245 }
246
247 pub(crate) fn get_account_asset(
250 conn: &mut Connection,
251 account_id: AccountId,
252 asset_id: AssetId,
253 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
254 let db_tx = conn.transaction().into_store_error()?;
256 let header = Self::get_account_header(&db_tx, account_id)?
257 .ok_or(StoreError::AccountDataNotFound(account_id))?
258 .0;
259 let smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
260
261 match smt_forest.get_asset_and_witness(account_id, header.vault_root(), asset_id) {
262 Ok((asset, witness)) => Ok(Some((asset, witness))),
263 Err(StoreError::VaultKeyNotTracked(..)) => Ok(None),
264 Err(err) => Err(err),
265 }
266 }
267
268 pub(crate) fn get_account_map_item(
271 conn: &mut Connection,
272 account_id: AccountId,
273 slot_name: StorageSlotName,
274 key: StorageMapKey,
275 ) -> Result<(Word, StorageMapWitness), StoreError> {
276 let db_tx = conn.transaction().into_store_error()?;
278 let header = Self::get_account_header(&db_tx, account_id)?
279 .ok_or(StoreError::AccountDataNotFound(account_id))?
280 .0;
281
282 let mut storage_values = query_storage_values(&db_tx, account_id)?;
283 let (slot_type, map_root) = storage_values
284 .remove(&slot_name)
285 .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))?;
286 if slot_type != StorageSlotType::Map {
287 return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name)));
288 }
289
290 let smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
291
292 let witness =
293 smt_forest.get_storage_map_item_witness(account_id, &slot_name, map_root, key)?;
294 let item = witness.get(key).unwrap_or(miden_client::EMPTY_WORD);
295
296 Ok((item, witness))
297 }
298
299 pub(crate) fn get_vault_asset_witnesses(
305 conn: &mut Connection,
306 account_id: AccountId,
307 vault_root: Word,
308 asset_ids: BTreeSet<AssetId>,
309 ) -> Result<Vec<AssetWitness>, StoreError> {
310 let db_tx = conn.transaction().into_store_error()?;
311 let smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
312 smt_forest.open_vault_asset_witnesses(account_id, vault_root, asset_ids)
313 }
314
315 pub(crate) fn get_account_addresses(
316 conn: &mut Connection,
317 account_id: AccountId,
318 ) -> Result<Vec<Address>, StoreError> {
319 query_account_addresses(conn, account_id)
320 }
321
322 pub(crate) fn get_account_code_by_id(
324 conn: &mut Connection,
325 account_id: AccountId,
326 ) -> Result<Option<AccountCode>, StoreError> {
327 let Some((header, ..)) =
328 query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?
329 .into_iter()
330 .next()
331 else {
332 return Ok(None);
333 };
334
335 query_account_code(conn, header.code_commitment())
336 }
337
338 pub(crate) fn insert_account(
342 conn: &mut Connection,
343 account: &Account,
344 initial_address: &Address,
345 client_account_type: ClientAccountType,
346 ) -> Result<(), StoreError> {
347 let db_tx = conn
348 .transaction_with_behavior(TransactionBehavior::Immediate)
349 .into_store_error()?;
350 {
351 let mut smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
352 Self::insert_account_code(&db_tx, account.code())?;
353
354 let account_id = account.id();
355 Self::insert_storage_slots(&db_tx, account_id, account.storage().slots().iter())?;
356 Self::insert_assets(&db_tx, account_id, account.vault().assets())?;
357 let watched = matches!(client_account_type, ClientAccountType::Watched);
358 Self::insert_new_account_header(&db_tx, &account.into(), account.seed(), watched)?;
359 Self::insert_address(&db_tx, initial_address, account.id())?;
360
361 Self::reconcile_account_forest(
362 &db_tx,
363 &mut smt_forest,
364 account_id,
365 account.vault(),
366 account.storage(),
367 )?;
368 }
369 db_tx.commit().into_store_error()
370 }
371
372 pub(crate) fn update_account(
373 conn: &mut Connection,
374 new_account_state: &Account,
375 ) -> Result<(), StoreError> {
376 const QUERY: &str = "SELECT id FROM latest_account_headers WHERE id = ?";
377 if conn
378 .prepare(QUERY)
379 .into_store_error()?
380 .query_map(params![new_account_state.id().to_bytes()], |row| row.get(0))
381 .into_store_error()?
382 .map(|result| {
383 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
384 |id: Vec<u8>| {
385 AccountId::read_from_bytes(&id)
386 .map_err(StoreError::DataDeserializationError)
387 },
388 )
389 })
390 .next()
391 .is_none()
392 {
393 return Err(StoreError::AccountDataNotFound(new_account_state.id()));
394 }
395
396 let db_tx = conn
397 .transaction_with_behavior(TransactionBehavior::Immediate)
398 .into_store_error()?;
399 {
400 let mut smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
401 Self::update_account_state(&db_tx, &mut smt_forest, new_account_state)?;
402 }
403 db_tx.commit().into_store_error()
404 }
405
406 pub fn upsert_foreign_account_code(
407 conn: &mut Connection,
408 account_id: AccountId,
409 code: &AccountCode,
410 ) -> Result<(), StoreError> {
411 let tx = conn.transaction().into_store_error()?;
412
413 Self::insert_account_code(&tx, code)?;
414
415 const QUERY: &str =
416 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
417
418 tx.execute(QUERY, params![account_id.to_bytes(), code.commitment().to_bytes()])
419 .into_store_error()?;
420
421 Self::insert_account_code(&tx, code)?;
422 tx.commit().into_store_error()
423 }
424
425 pub(crate) fn insert_address(
426 tx: &Transaction<'_>,
427 address: &Address,
428 account_id: AccountId,
429 ) -> Result<(), StoreError> {
430 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
431 let serialized_address = address.to_bytes();
432 tx.execute(QUERY, params![serialized_address, account_id.to_bytes(),])
433 .into_store_error()?;
434
435 Ok(())
436 }
437
438 pub(crate) fn remove_address(
440 conn: &mut Connection,
441 address: &Address,
442 ) -> Result<bool, StoreError> {
443 let tx = conn.transaction().into_store_error()?;
444 let serialized_address = address.to_bytes();
445 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
446 let count = tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
447
448 tx.commit().into_store_error()?;
449
450 Ok(count > 0)
451 }
452
453 pub(crate) fn insert_account_code(
455 tx: &Transaction<'_>,
456 account_code: &AccountCode,
457 ) -> Result<(), StoreError> {
458 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
459 tx.execute(QUERY, params![account_code.commitment().to_bytes(), account_code.to_bytes()])
460 .into_store_error()?;
461 Ok(())
462 }
463
464 pub(crate) fn apply_account_patch(
468 tx: &Transaction<'_>,
469 smt_forest: &mut ScopedAccountForest<'_, '_>,
470 init_account_state: &AccountHeader,
471 final_account_state: &AccountHeader,
472 patch: &AccountPatch,
473 ) -> Result<(), StoreError> {
474 let account_id = final_account_state.id();
475
476 let stored_header = Self::require_latest_account_header(tx, account_id)?;
481 if stored_header.to_commitment() != init_account_state.to_commitment() {
482 return Err(StoreError::DatabaseError(format!(
483 "apply_account_patch: stored state {} for account {} does not match the patch's \
484 initial state {}",
485 stored_header.to_commitment(),
486 account_id,
487 init_account_state.to_commitment(),
488 )));
489 }
490
491 Self::replace_account_header(tx, final_account_state, init_account_state)?;
493
494 Self::apply_account_vault_patch(tx, account_id, final_account_state, patch.vault())?;
495
496 let mut update = AccountUpdate::new();
499 update.vault_patch(account_id, patch.vault(), final_account_state.vault_root());
500 update.storage_patch(account_id, patch.storage());
501
502 let revision = allocate_forest_revision(tx).into_store_error()?;
503 smt_forest.apply(revision, update)?;
504
505 Self::write_storage_patch(
506 tx,
507 smt_forest,
508 account_id,
509 final_account_state.nonce().as_canonical_u64(),
510 patch.storage(),
511 )?;
512 Self::verify_storage_commitment(tx, account_id, final_account_state.storage_commitment())?;
513
514 Ok(())
515 }
516
517 pub(crate) fn reconcile_account_forest(
522 tx: &Transaction<'_>,
523 smt_forest: &mut ScopedAccountForest<'_, '_>,
524 account_id: AccountId,
525 vault: &AssetVault,
526 storage: &AccountStorage,
527 ) -> Result<(), StoreError> {
528 let mut update = AccountUpdate::new();
529 update.full_state(account_id, vault.assets(), storage.slots().iter());
530
531 for slot_name in Self::query_map_slot_names(tx, account_id)? {
535 update.clear_map(account_id, &slot_name);
536 }
537
538 Self::apply_forest_update(tx, smt_forest, update)
539 }
540
541 fn reconcile_account_forest_from_tables(
548 tx: &Transaction<'_>,
549 smt_forest: &mut ScopedAccountForest<'_, '_>,
550 account_id: AccountId,
551 extra_map_slots: &[StorageSlotName],
552 ) -> Result<(), StoreError> {
553 let assets = query_vault_assets(tx, account_id)?;
554 let slots = query_storage_slots(tx, account_id, &AccountStorageFilter::All)?;
555
556 let mut update = AccountUpdate::new();
557 update.full_state(account_id, assets.into_iter(), slots.values());
558 for slot_name in extra_map_slots {
559 update.clear_map(account_id, slot_name);
560 }
561
562 Self::apply_forest_update(tx, smt_forest, update)
563 }
564
565 fn verify_storage_commitment(
570 tx: &Transaction<'_>,
571 account_id: AccountId,
572 expected: Word,
573 ) -> Result<(), StoreError> {
574 let mut slot_headers: Vec<StorageSlotHeader> = query_storage_values(tx, account_id)?
575 .into_iter()
576 .map(|(slot_name, (slot_type, value))| {
577 StorageSlotHeader::new(slot_name, slot_type, value)
578 })
579 .collect();
580 slot_headers.sort_by_key(StorageSlotHeader::id);
581
582 let actual = AccountStorageHeader::new(slot_headers)
583 .map_err(StoreError::AccountError)?
584 .to_commitment();
585 if actual != expected {
586 return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
587 expected_root: expected,
588 actual_root: actual,
589 }));
590 }
591
592 Ok(())
593 }
594
595 fn apply_forest_update(
597 tx: &Transaction<'_>,
598 smt_forest: &mut ScopedAccountForest<'_, '_>,
599 update: AccountUpdate,
600 ) -> Result<(), StoreError> {
601 let revision = allocate_forest_revision(tx).into_store_error()?;
602 smt_forest.apply(revision, update)
603 }
604
605 fn require_latest_account_header(
608 tx: &Transaction<'_>,
609 account_id: AccountId,
610 ) -> Result<AccountHeader, StoreError> {
611 query_latest_account_headers(tx, "id = ?", params![account_id.to_bytes()])?
612 .into_iter()
613 .next()
614 .map(|(header, ..)| header)
615 .ok_or(StoreError::AccountDataNotFound(account_id))
616 }
617
618 fn query_map_slot_names(
620 tx: &Transaction<'_>,
621 account_id: AccountId,
622 ) -> Result<Vec<StorageSlotName>, StoreError> {
623 let mut stmt = tx
624 .prepare(
625 "SELECT DISTINCT slot_name FROM latest_storage_map_entries WHERE account_id = ?",
626 )
627 .into_store_error()?;
628 let rows = stmt
629 .query_map(params![account_id.to_bytes()], |row| row.get::<_, String>(0))
630 .into_store_error()?;
631
632 let mut names = Vec::new();
633 for row in rows {
634 names.push(
635 StorageSlotName::new(row.into_store_error()?)
636 .map_err(|e| StoreError::ParsingError(e.to_string()))?,
637 );
638 }
639 Ok(names)
640 }
641
642 pub(crate) fn undo_account_state(
644 tx: &Transaction<'_>,
645 smt_forest: &mut ScopedAccountForest<'_, '_>,
646 discarded_states: &[(AccountId, Word)],
647 ) -> Result<(), StoreError> {
648 if discarded_states.is_empty() {
649 return Ok(());
650 }
651
652 let commitment_params = Rc::new(
653 discarded_states
654 .iter()
655 .map(|(_, commitment)| Value::Blob(commitment.to_bytes()))
656 .collect::<Vec<_>>(),
657 );
658
659 let mut id_nonce_pairs: Vec<(Vec<u8>, u64)> = Vec::new();
662 for query in [
663 "SELECT id, nonce FROM latest_account_headers WHERE account_commitment IN rarray(?)",
664 "SELECT id, nonce FROM historical_account_headers WHERE account_commitment IN rarray(?)",
665 ] {
666 id_nonce_pairs.extend(
667 tx.prepare(query)
668 .into_store_error()?
669 .query_map(params![commitment_params.clone()], |row| {
670 let id: Vec<u8> = row.get(0)?;
671 let nonce: u64 = column_value_as_u64(row, 1)?;
672 Ok((id, nonce))
673 })
674 .into_store_error()?
675 .filter_map(Result::ok),
676 );
677 }
678
679 let mut nonces_by_account: BTreeMap<Vec<u8>, Vec<u64>> = BTreeMap::new();
684 for (id, nonce) in &id_nonce_pairs {
685 nonces_by_account.entry(id.clone()).or_default().push(*nonce);
686 }
687 for nonces in nonces_by_account.values_mut() {
688 nonces.sort_unstable();
689 nonces.dedup();
690 nonces.reverse();
691 }
692
693 let mut pre_undo_map_slots: BTreeMap<Vec<u8>, Vec<StorageSlotName>> = BTreeMap::new();
695 for account_id_bytes in nonces_by_account.keys() {
696 let account_id = AccountId::read_from_bytes(account_id_bytes)?;
697 pre_undo_map_slots
698 .insert(account_id_bytes.clone(), Self::query_map_slot_names(tx, account_id)?);
699 }
700
701 for (account_id_bytes, nonces) in &nonces_by_account {
703 Self::undo_account_nonces(tx, account_id_bytes, nonces)?;
704 }
705
706 for account_id_bytes in nonces_by_account.keys() {
708 let account_id = AccountId::read_from_bytes(account_id_bytes)?;
709 let stale_slots: &[StorageSlotName] =
710 pre_undo_map_slots.get(account_id_bytes).map_or(&[], Vec::as_slice);
711 Self::reconcile_account_forest_from_tables(tx, smt_forest, account_id, stale_slots)?;
712 }
713
714 Ok(())
715 }
716
717 fn undo_account_nonces(
720 tx: &Transaction<'_>,
721 account_id_bytes: &[u8],
722 nonces: &[u64],
723 ) -> Result<(), StoreError> {
724 for &nonce in nonces {
726 let nonce_val = u64_to_value(nonce);
727 Self::restore_old_values_for_nonce(tx, account_id_bytes, &nonce_val)?;
728 }
729
730 let min_nonce = *nonces.last().unwrap();
735 let min_nonce_val = u64_to_value(min_nonce);
736
737 let old_header_exists: bool = tx
738 .query_row(
739 "SELECT COUNT(*) FROM historical_account_headers \
740 WHERE id = ? AND replaced_at_nonce = ?",
741 params![account_id_bytes, &min_nonce_val],
742 |row| row.get::<_, i64>(0),
743 )
744 .into_store_error()?
745 > 0;
746
747 if old_header_exists {
748 tx.execute(
752 "INSERT OR REPLACE INTO latest_account_headers \
753 (id, account_commitment, code_commitment, storage_commitment, \
754 vault_root, nonce, account_seed, locked) \
755 SELECT id, account_commitment, code_commitment, storage_commitment, \
756 vault_root, nonce, account_seed, locked \
757 FROM historical_account_headers \
758 WHERE id = ? AND replaced_at_nonce = ?",
759 params![account_id_bytes, &min_nonce_val],
760 )
761 .into_store_error()?;
762 } else {
763 for table in [
765 "DELETE FROM latest_account_headers WHERE id = ?",
766 "DELETE FROM latest_account_storage WHERE account_id = ?",
767 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
768 "DELETE FROM latest_account_assets WHERE account_id = ?",
769 ] {
770 tx.execute(table, params![account_id_bytes]).into_store_error()?;
771 }
772 }
773
774 let nonce_params = Rc::new(nonces.iter().map(|n| u64_to_value(*n)).collect::<Vec<_>>());
776 for table in [
777 "historical_account_storage",
778 "historical_storage_map_entries",
779 "historical_account_assets",
780 ] {
781 tx.execute(
782 &format!(
783 "DELETE FROM {table} WHERE account_id = ? AND replaced_at_nonce IN rarray(?)"
784 ),
785 params![account_id_bytes, nonce_params.clone()],
786 )
787 .into_store_error()?;
788 }
789 tx.execute(
790 "DELETE FROM historical_account_headers \
791 WHERE id = ? AND replaced_at_nonce IN rarray(?)",
792 params![account_id_bytes, nonce_params],
793 )
794 .into_store_error()?;
795
796 Ok(())
797 }
798
799 fn restore_old_values_for_nonce(
802 tx: &Transaction<'_>,
803 account_id_bytes: &[u8],
804 nonce_val: &rusqlite::types::Value,
805 ) -> Result<(), StoreError> {
806 tx.execute(
808 "INSERT OR REPLACE INTO latest_account_storage \
809 (account_id, slot_name, slot_value, slot_type) \
810 SELECT account_id, slot_name, old_slot_value, slot_type \
811 FROM historical_account_storage \
812 WHERE account_id = ? AND replaced_at_nonce = ? AND old_slot_value IS NOT NULL",
813 params![account_id_bytes, nonce_val],
814 )
815 .into_store_error()?;
816
817 tx.execute(
819 "DELETE FROM latest_account_storage \
820 WHERE account_id = ?1 AND slot_name IN (\
821 SELECT slot_name FROM historical_account_storage \
822 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_slot_value IS NULL\
823 )",
824 params![account_id_bytes, nonce_val],
825 )
826 .into_store_error()?;
827
828 tx.execute(
830 "INSERT OR REPLACE INTO latest_storage_map_entries \
831 (account_id, slot_name, key, value) \
832 SELECT account_id, slot_name, key, old_value \
833 FROM historical_storage_map_entries \
834 WHERE account_id = ? AND replaced_at_nonce = ? AND old_value IS NOT NULL",
835 params![account_id_bytes, nonce_val],
836 )
837 .into_store_error()?;
838
839 tx.execute(
841 "DELETE FROM latest_storage_map_entries \
842 WHERE account_id = ?1 AND EXISTS (\
843 SELECT 1 FROM historical_storage_map_entries h \
844 WHERE h.account_id = latest_storage_map_entries.account_id \
845 AND h.slot_name = latest_storage_map_entries.slot_name \
846 AND h.key = latest_storage_map_entries.key \
847 AND h.replaced_at_nonce = ?2 AND h.old_value IS NULL\
848 )",
849 params![account_id_bytes, nonce_val],
850 )
851 .into_store_error()?;
852
853 tx.execute(
855 "INSERT OR REPLACE INTO latest_account_assets \
856 (account_id, asset_id, asset) \
857 SELECT account_id, asset_id, old_asset \
858 FROM historical_account_assets \
859 WHERE account_id = ? AND replaced_at_nonce = ? AND old_asset IS NOT NULL",
860 params![account_id_bytes, nonce_val],
861 )
862 .into_store_error()?;
863
864 tx.execute(
866 "DELETE FROM latest_account_assets \
867 WHERE account_id = ?1 AND asset_id IN (\
868 SELECT asset_id FROM historical_account_assets \
869 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_asset IS NULL\
870 )",
871 params![account_id_bytes, nonce_val],
872 )
873 .into_store_error()?;
874
875 Ok(())
876 }
877
878 pub(crate) fn update_account_state(
883 tx: &Transaction<'_>,
884 smt_forest: &mut ScopedAccountForest<'_, '_>,
885 new_account_state: &Account,
886 ) -> Result<(), StoreError> {
887 let account_id = new_account_state.id();
888 let account_id_bytes = account_id.to_bytes();
889
890 let old_header = query_latest_account_headers(tx, "id = ?", params![&account_id_bytes])?
893 .into_iter()
894 .next()
895 .map(|(header, ..)| header)
896 .ok_or(StoreError::AccountDataNotFound(account_id))?;
897
898 if new_account_state.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
899 return Err(StoreError::DatabaseError(format!(
900 "update_account_state: new nonce {} is less than old nonce {} for account {}",
901 new_account_state.nonce().as_canonical_u64(),
902 old_header.nonce().as_canonical_u64(),
903 account_id,
904 )));
905 }
906
907 let nonce_val = u64_to_value(new_account_state.nonce().as_canonical_u64());
908
909 Self::reconcile_account_forest(
911 tx,
912 smt_forest,
913 account_id,
914 new_account_state.vault(),
915 new_account_state.storage(),
916 )?;
917
918 tx.execute(
920 "INSERT OR REPLACE INTO historical_account_storage \
921 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
922 SELECT account_id, ?, slot_name, slot_value, slot_type \
923 FROM latest_account_storage WHERE account_id = ?",
924 params![&nonce_val, &account_id_bytes],
925 )
926 .into_store_error()?;
927 tx.execute(
928 "INSERT OR REPLACE INTO historical_storage_map_entries \
929 (account_id, replaced_at_nonce, slot_name, key, old_value) \
930 SELECT account_id, ?, slot_name, key, value \
931 FROM latest_storage_map_entries WHERE account_id = ?",
932 params![&nonce_val, &account_id_bytes],
933 )
934 .into_store_error()?;
935 tx.execute(
936 "INSERT OR REPLACE INTO historical_account_assets \
937 (account_id, replaced_at_nonce, asset_id, old_asset) \
938 SELECT account_id, ?, asset_id, asset \
939 FROM latest_account_assets WHERE account_id = ?",
940 params![&nonce_val, &account_id_bytes],
941 )
942 .into_store_error()?;
943
944 tx.execute(
946 "DELETE FROM latest_account_storage WHERE account_id = ?",
947 params![&account_id_bytes],
948 )
949 .into_store_error()?;
950 tx.execute(
951 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
952 params![&account_id_bytes],
953 )
954 .into_store_error()?;
955 tx.execute(
956 "DELETE FROM latest_account_assets WHERE account_id = ?",
957 params![&account_id_bytes],
958 )
959 .into_store_error()?;
960
961 Self::insert_storage_slots(tx, account_id, new_account_state.storage().slots().iter())?;
963 Self::insert_assets(tx, account_id, new_account_state.vault().assets())?;
964
965 tx.execute(
968 "INSERT OR IGNORE INTO historical_account_storage \
969 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
970 SELECT account_id, ?, slot_name, NULL, slot_type \
971 FROM latest_account_storage WHERE account_id = ?",
972 params![&nonce_val, &account_id_bytes],
973 )
974 .into_store_error()?;
975 tx.execute(
976 "INSERT OR IGNORE INTO historical_storage_map_entries \
977 (account_id, replaced_at_nonce, slot_name, key, old_value) \
978 SELECT account_id, ?, slot_name, key, NULL \
979 FROM latest_storage_map_entries WHERE account_id = ?",
980 params![&nonce_val, &account_id_bytes],
981 )
982 .into_store_error()?;
983 tx.execute(
984 "INSERT OR IGNORE INTO historical_account_assets \
985 (account_id, replaced_at_nonce, asset_id, old_asset) \
986 SELECT account_id, ?, asset_id, NULL \
987 FROM latest_account_assets WHERE account_id = ?",
988 params![&nonce_val, &account_id_bytes],
989 )
990 .into_store_error()?;
991
992 Self::replace_account_header(tx, &new_account_state.into(), &old_header)?;
994
995 Ok(())
996 }
997
998 pub(crate) fn apply_sync_account_patch(
1000 tx: &Transaction<'_>,
1001 smt_forest: &mut ScopedAccountForest<'_, '_>,
1002 new_header: &AccountHeader,
1003 patch: &AccountPatch,
1004 ) -> Result<(), StoreError> {
1005 let account_id = new_header.id();
1006
1007 let init_header = Self::require_latest_account_header(tx, account_id)?;
1009
1010 if new_header.nonce().as_canonical_u64() <= init_header.nonce().as_canonical_u64() {
1011 return Err(StoreError::DatabaseError(format!(
1012 "apply_sync_account_patch: new nonce {} is not greater than local nonce {} for account {}",
1013 new_header.nonce().as_canonical_u64(),
1014 init_header.nonce().as_canonical_u64(),
1015 account_id,
1016 )));
1017 }
1018
1019 Self::apply_account_patch(tx, smt_forest, &init_header, new_header, patch)
1022 }
1023
1024 pub(crate) fn lock_account_on_unexpected_commitment(
1027 tx: &Transaction<'_>,
1028 account_id: &AccountId,
1029 mismatched_digest: &Word,
1030 ) -> Result<(), StoreError> {
1031 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)";
1035 let account_id_bytes = account_id.to_bytes();
1036 let digest_bytes = mismatched_digest.to_bytes();
1037 let params = named_params! {
1038 ":account_id": account_id_bytes,
1039 ":digest": digest_bytes
1040 };
1041
1042 let query = format!("UPDATE latest_account_headers SET locked = true {LOCK_CONDITION}");
1043 tx.execute(&query, params).into_store_error()?;
1044
1045 let query = format!("UPDATE historical_account_headers SET locked = true {LOCK_CONDITION}");
1047 tx.execute(&query, params).into_store_error()?;
1048
1049 Ok(())
1050 }
1051
1052 fn insert_new_account_header(
1061 tx: &Transaction<'_>,
1062 new_header: &AccountHeader,
1063 account_seed: Option<Word>,
1064 watched: bool,
1065 ) -> Result<(), StoreError> {
1066 let id = new_header.id().to_bytes();
1067 let code_commitment = new_header.code_commitment().to_bytes();
1068 let storage_commitment = new_header.storage_commitment().to_bytes();
1069 let vault_root = new_header.vault_root().to_bytes();
1070 let nonce = u64_to_value(new_header.nonce().as_canonical_u64());
1071 let commitment = new_header.to_commitment().to_bytes();
1072 let account_seed = account_seed.map(|seed| seed.to_bytes());
1073
1074 const LATEST_QUERY: &str = insert_sql!(
1075 latest_account_headers {
1076 id,
1077 code_commitment,
1078 storage_commitment,
1079 vault_root,
1080 nonce,
1081 account_seed,
1082 account_commitment,
1083 locked,
1084 watched
1085 } | REPLACE
1086 );
1087
1088 tx.execute(
1089 LATEST_QUERY,
1090 params![
1091 id,
1092 code_commitment,
1093 storage_commitment,
1094 vault_root,
1095 nonce,
1096 account_seed,
1097 commitment,
1098 false,
1099 watched,
1100 ],
1101 )
1102 .into_store_error()?;
1103
1104 Ok(())
1105 }
1106
1107 fn replace_account_header(
1113 tx: &Transaction<'_>,
1114 new_header: &AccountHeader,
1115 old_header: &AccountHeader,
1116 ) -> Result<(), StoreError> {
1117 if new_header.id() != old_header.id() {
1118 return Err(StoreError::DatabaseError(format!(
1119 "replace_account_header: account id mismatch (new: {}, old: {})",
1120 new_header.id(),
1121 old_header.id(),
1122 )));
1123 }
1124 if new_header.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
1125 return Err(StoreError::DatabaseError(format!(
1126 "replace_account_header: new nonce {} is less than old nonce {} for account {}",
1127 new_header.nonce().as_canonical_u64(),
1128 old_header.nonce().as_canonical_u64(),
1129 new_header.id(),
1130 )));
1131 }
1132
1133 let id_bytes = new_header.id().to_bytes();
1134
1135 let (old_seed, old_locked, old_watched): (Option<Vec<u8>>, bool, bool) = tx
1139 .query_row(
1140 "SELECT account_seed, locked, watched FROM latest_account_headers WHERE id = ?",
1141 params![&id_bytes],
1142 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
1143 )
1144 .optional()
1145 .into_store_error()?
1146 .unwrap_or((None, false, false));
1147
1148 let old_id = old_header.id().to_bytes();
1150 let old_code_commitment = old_header.code_commitment().to_bytes();
1151 let old_storage_commitment = old_header.storage_commitment().to_bytes();
1152 let old_vault_root = old_header.vault_root().to_bytes();
1153 let old_nonce = u64_to_value(old_header.nonce().as_canonical_u64());
1154 let old_commitment = old_header.to_commitment().to_bytes();
1155 let replaced_at_nonce = u64_to_value(new_header.nonce().as_canonical_u64());
1156
1157 const HISTORICAL_QUERY: &str = insert_sql!(
1158 historical_account_headers {
1159 id,
1160 code_commitment,
1161 storage_commitment,
1162 vault_root,
1163 nonce,
1164 account_seed,
1165 account_commitment,
1166 locked,
1167 replaced_at_nonce
1168 } | REPLACE
1169 );
1170
1171 tx.execute(
1172 HISTORICAL_QUERY,
1173 params![
1174 old_id,
1175 old_code_commitment,
1176 old_storage_commitment,
1177 old_vault_root,
1178 old_nonce,
1179 old_seed,
1180 old_commitment,
1181 old_locked,
1182 replaced_at_nonce,
1183 ],
1184 )
1185 .into_store_error()?;
1186
1187 Self::insert_new_account_header(tx, new_header, None, old_watched)
1189 }
1190
1191 pub fn prune_account_history(
1197 conn: &mut Connection,
1198 account_id: AccountId,
1199 up_to_nonce: Felt,
1200 ) -> Result<usize, StoreError> {
1201 let tx = conn.transaction().into_store_error()?;
1202 let account_id_bytes = account_id.to_bytes();
1203 let boundary_val = u64_to_value(up_to_nonce.as_canonical_u64());
1204 let mut total_deleted: usize = 0;
1205
1206 let candidate_code_commitments: Vec<Vec<u8>> = {
1208 let mut stmt = tx
1209 .prepare(
1210 "SELECT DISTINCT code_commitment FROM historical_account_headers \
1211 WHERE id = ? AND replaced_at_nonce <= ?",
1212 )
1213 .into_store_error()?;
1214 let rows = stmt
1215 .query_map(params![&account_id_bytes, &boundary_val], |row| row.get(0))
1216 .into_store_error()?;
1217 rows.collect::<Result<Vec<Vec<u8>>, _>>().into_store_error()?
1218 };
1219
1220 total_deleted += tx
1222 .execute(
1223 "DELETE FROM historical_account_headers \
1224 WHERE id = ? AND replaced_at_nonce <= ?",
1225 params![&account_id_bytes, &boundary_val],
1226 )
1227 .into_store_error()?;
1228
1229 total_deleted += tx
1230 .execute(
1231 "DELETE FROM historical_account_storage \
1232 WHERE account_id = ? AND replaced_at_nonce <= ?",
1233 params![&account_id_bytes, &boundary_val],
1234 )
1235 .into_store_error()?;
1236
1237 total_deleted += tx
1238 .execute(
1239 "DELETE FROM historical_storage_map_entries \
1240 WHERE account_id = ? AND replaced_at_nonce <= ?",
1241 params![&account_id_bytes, &boundary_val],
1242 )
1243 .into_store_error()?;
1244
1245 total_deleted += tx
1246 .execute(
1247 "DELETE FROM historical_account_assets \
1248 WHERE account_id = ? AND replaced_at_nonce <= ?",
1249 params![&account_id_bytes, &boundary_val],
1250 )
1251 .into_store_error()?;
1252
1253 for commitment in &candidate_code_commitments {
1256 let still_referenced: bool = tx
1257 .query_row(
1258 "SELECT EXISTS(
1259 SELECT 1 FROM latest_account_headers WHERE code_commitment = ?1
1260 UNION ALL
1261 SELECT 1 FROM historical_account_headers WHERE code_commitment = ?1
1262 UNION ALL
1263 SELECT 1 FROM foreign_account_code WHERE code_commitment = ?1
1264 )",
1265 params![commitment],
1266 |row| row.get(0),
1267 )
1268 .into_store_error()?;
1269
1270 if !still_referenced {
1271 total_deleted += tx
1272 .execute("DELETE FROM account_code WHERE commitment = ?", params![commitment])
1273 .into_store_error()?;
1274 }
1275 }
1276
1277 tx.commit().into_store_error()?;
1278 Ok(total_deleted)
1279 }
1280}