1use std::collections::{BTreeMap, BTreeSet};
4use std::string::ToString;
5use std::vec::Vec;
6
7use miden_client::account::{
8 Account,
9 AccountCode,
10 AccountHeader,
11 AccountId,
12 AccountPatch,
13 AccountStorage,
14 Address,
15 PartialAccount,
16 PartialStorage,
17 PartialStorageMap,
18 StorageMapKey,
19 StorageSlotName,
20 StorageSlotType,
21};
22use miden_client::asset::{Asset, AssetVault, AssetWitness};
23use miden_client::store::{
24 AccountRecord,
25 AccountRecordData,
26 AccountStatus,
27 AccountStorageFilter,
28 AccountUpdate,
29 ClientAccountType,
30 StoreError,
31};
32use miden_client::utils::{Deserializable, Serializable};
33use miden_client::{AccountError, Felt, Word};
34use miden_protocol::account::{AccountStorageHeader, StorageMapWitness, StorageSlotHeader};
35use miden_protocol::asset::{AssetId, PartialVault};
36use miden_protocol::crypto::merkle::MerkleError;
37use rusqlite::{Connection, OptionalExtension, Transaction, named_params, params};
38
39use crate::account::rows::{
40 query_account_addresses,
41 query_account_code,
42 query_historical_account_headers,
43 query_latest_account_headers,
44 query_storage_slots,
45 query_storage_values,
46 query_vault_assets,
47};
48use crate::forest::{ScopedAccountForest, SqliteForestBackend, allocate_forest_revision};
49use crate::sql_error::SqlResultExt;
50use crate::{
51 SqliteStore,
52 blob_array,
53 column_value_as_u64,
54 insert_sql,
55 int_array,
56 proto,
57 subst,
58 u64_to_value,
59 with_write_tx,
60};
61
62impl SqliteStore {
63 pub(crate) fn get_account_ids(conn: &mut Connection) -> Result<Vec<AccountId>, StoreError> {
67 const QUERY: &str = "SELECT id FROM latest_account_headers";
68
69 conn.prepare_cached(QUERY)
70 .into_store_error()?
71 .query_map([], |row| row.get(0))
72 .expect("no binding parameters used in query")
73 .map(|result| {
74 let id: Vec<u8> = result.into_store_error()?;
75 Ok(AccountId::read_from_bytes(&id)?)
76 })
77 .collect::<Result<Vec<AccountId>, StoreError>>()
78 }
79
80 pub(crate) fn get_account_headers(
81 conn: &mut Connection,
82 ) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
83 Ok(query_latest_account_headers(conn, "1=1 ORDER BY id", params![])?
84 .into_iter()
85 .map(|(header, status, _)| (header, status))
86 .collect())
87 }
88
89 pub(crate) fn get_account_header(
90 conn: &Connection,
91 account_id: AccountId,
92 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
93 Ok(query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?
94 .pop()
95 .map(|(header, status, _)| (header, status)))
96 }
97
98 pub(crate) fn get_account_header_by_commitment(
99 conn: &mut Connection,
100 account_commitment: Word,
101 ) -> Result<Option<AccountHeader>, StoreError> {
102 Ok(query_historical_account_headers(
103 conn,
104 "account_commitment = ?",
105 params![account_commitment.to_bytes()],
106 )?
107 .pop()
108 .map(|(header, _)| header))
109 }
110
111 pub(crate) fn get_account(
113 conn: &mut Connection,
114 account_id: AccountId,
115 ) -> Result<Option<AccountRecord>, StoreError> {
116 let Some((header, status, client_account_type)) =
117 query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?.pop()
118 else {
119 return Ok(None);
120 };
121
122 let assets = query_vault_assets(conn, account_id)?;
123 let vault = AssetVault::new(&assets)?;
124
125 let slots = query_storage_slots(conn, account_id, &AccountStorageFilter::All)?
126 .into_values()
127 .collect();
128
129 let storage = AccountStorage::new(slots)?;
130
131 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
132 return Ok(None);
133 };
134
135 let account = Account::new_unchecked(
136 header.id(),
137 vault,
138 storage,
139 account_code,
140 header.nonce(),
141 status.seed().copied(),
142 );
143
144 let account_data = AccountRecordData::Full(account);
145 Ok(Some(AccountRecord::new(account_data, status, client_account_type)))
146 }
147
148 pub(crate) fn get_minimal_partial_account(
150 conn: &mut Connection,
151 account_id: AccountId,
152 ) -> Result<Option<AccountRecord>, StoreError> {
153 let Some((header, status, client_account_type)) =
154 query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?.pop()
155 else {
156 return Ok(None);
157 };
158
159 let partial_vault = PartialVault::new(header.vault_root());
161
162 let mut storage_header = Vec::new();
164 let mut maps = vec![];
165
166 let storage_values = query_storage_values(conn, account_id)?;
167
168 for (slot_name, (slot_type, value)) in storage_values {
172 storage_header.push(StorageSlotHeader::new(slot_name.clone(), slot_type, value));
173 if slot_type == StorageSlotType::Map {
174 maps.push(PartialStorageMap::new(value));
175 }
176 }
177 storage_header.sort_by_key(StorageSlotHeader::id);
178 let storage_header =
179 AccountStorageHeader::new(storage_header).map_err(StoreError::AccountError)?;
180 let partial_storage =
181 PartialStorage::new(storage_header, maps).map_err(StoreError::AccountError)?;
182
183 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
184 return Ok(None);
185 };
186
187 let partial_account = PartialAccount::new(
188 header.id(),
189 header.nonce(),
190 account_code,
191 partial_storage,
192 partial_vault,
193 status.seed().copied(),
194 )?;
195 let account_record_data = AccountRecordData::Partial(partial_account);
196 Ok(Some(AccountRecord::new(account_record_data, status, client_account_type)))
197 }
198
199 pub fn get_foreign_account_code(
200 conn: &mut Connection,
201 account_ids: Vec<AccountId>,
202 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
203 let account_id_list = blob_array(account_ids);
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([account_id_list], |row| Ok((row.get("account_id")?, row.get("code")?)))
212 .into_store_error()?
213 .map(|result| {
214 let (id, code): (Vec<u8>, Vec<u8>) = result.into_store_error()?;
215 Ok((AccountId::read_from_bytes(&id)?, proto::decode_unchecked(&code)?))
216 })
217 .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
218 }
219
220 pub fn get_account_vault(
222 conn: &Connection,
223 account_id: AccountId,
224 ) -> Result<AssetVault, StoreError> {
225 let assets = query_vault_assets(conn, account_id)?;
226 Ok(AssetVault::new(&assets)?)
227 }
228
229 pub fn get_account_storage(
231 conn: &Connection,
232 account_id: AccountId,
233 filter: &AccountStorageFilter,
234 ) -> Result<AccountStorage, StoreError> {
235 let slots = query_storage_slots(conn, account_id, filter)?.into_values().collect();
236 Ok(AccountStorage::new(slots)?)
237 }
238
239 pub(crate) fn get_account_asset(
242 conn: &mut Connection,
243 account_id: AccountId,
244 asset_id: AssetId,
245 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
246 let db_tx = conn.transaction().into_store_error()?;
248 let header = Self::require_latest_account_header(&db_tx, account_id)?;
249 let smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
250
251 match smt_forest.get_asset_and_witness(account_id, header.vault_root(), asset_id) {
252 Ok((asset, witness)) => Ok(Some((asset, witness))),
253 Err(StoreError::VaultKeyNotTracked(..)) => Ok(None),
254 Err(err) => Err(err),
255 }
256 }
257
258 pub(crate) fn get_account_map_item(
261 conn: &mut Connection,
262 account_id: AccountId,
263 slot_name: StorageSlotName,
264 key: StorageMapKey,
265 ) -> Result<(Word, StorageMapWitness), StoreError> {
266 let db_tx = conn.transaction().into_store_error()?;
268 let header = Self::require_latest_account_header(&db_tx, account_id)?;
269
270 let mut storage_values = query_storage_values(&db_tx, account_id)?;
271 let (slot_type, map_root) = storage_values
272 .remove(&slot_name)
273 .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))?;
274 if slot_type != StorageSlotType::Map {
275 return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name)));
276 }
277
278 let smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
279
280 let witness =
281 smt_forest.get_storage_map_item_witness(account_id, &slot_name, map_root, key)?;
282 let item = witness.get(key).unwrap_or(miden_client::EMPTY_WORD);
283
284 Ok((item, witness))
285 }
286
287 pub(crate) fn get_vault_asset_witnesses(
293 conn: &mut Connection,
294 account_id: AccountId,
295 vault_root: Word,
296 asset_ids: BTreeSet<AssetId>,
297 ) -> Result<Vec<AssetWitness>, StoreError> {
298 let db_tx = conn.transaction().into_store_error()?;
299 let smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
300 smt_forest.open_vault_asset_witnesses(account_id, vault_root, asset_ids)
301 }
302
303 pub(crate) fn get_account_addresses(
304 conn: &mut Connection,
305 account_id: AccountId,
306 ) -> Result<Vec<Address>, StoreError> {
307 query_account_addresses(conn, account_id)
308 }
309
310 pub(crate) fn get_account_code_by_id(
312 conn: &mut Connection,
313 account_id: AccountId,
314 ) -> Result<Option<AccountCode>, StoreError> {
315 let Some((header, ..)) =
316 query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?
317 .into_iter()
318 .next()
319 else {
320 return Ok(None);
321 };
322
323 query_account_code(conn, header.code_commitment())
324 }
325
326 pub(crate) fn insert_account(
330 conn: &mut Connection,
331 account: &Account,
332 initial_address: &Address,
333 client_account_type: ClientAccountType,
334 ) -> Result<(), StoreError> {
335 with_write_tx(conn, |tx| {
336 let mut smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(tx))?;
337 Self::insert_account_code(tx, account.code())?;
338
339 let account_id = account.id();
340 Self::insert_storage_slots(tx, account_id, account.storage().slots().iter())?;
341 Self::insert_assets(tx, account_id, account.vault().assets())?;
342 let watched = matches!(client_account_type, ClientAccountType::Watched);
343 Self::insert_new_account_header(tx, &account.into(), account.seed(), watched)?;
344 Self::insert_address_tx(tx, initial_address, account.id())?;
345
346 Self::reconcile_account_forest(
347 tx,
348 &mut smt_forest,
349 account_id,
350 account.vault(),
351 account.storage(),
352 )
353 })
354 }
355
356 pub(crate) fn update_account(
357 conn: &mut Connection,
358 new_account_state: &Account,
359 ) -> Result<(), StoreError> {
360 with_write_tx(conn, |tx| {
361 let mut smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(tx))?;
362 Self::update_account_state(tx, &mut smt_forest, new_account_state)
363 })
364 }
365
366 pub(crate) fn upsert_foreign_account_code(
367 conn: &mut Connection,
368 account_id: AccountId,
369 code: &AccountCode,
370 ) -> Result<(), StoreError> {
371 with_write_tx(conn, |tx| {
372 Self::insert_account_code(tx, code)?;
373
374 const QUERY: &str =
375 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
376
377 tx.execute(QUERY, params![account_id.to_bytes(), code.commitment().to_bytes()])
378 .into_store_error()?;
379
380 Ok(())
381 })
382 }
383
384 pub(crate) fn insert_address(
385 conn: &mut Connection,
386 address: &Address,
387 account_id: AccountId,
388 ) -> Result<(), StoreError> {
389 with_write_tx(conn, |tx| Self::insert_address_tx(tx, address, account_id))
390 }
391
392 pub(crate) fn insert_address_tx(
393 tx: &Transaction<'_>,
394 address: &Address,
395 account_id: AccountId,
396 ) -> Result<(), StoreError> {
397 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
398 let serialized_address = address.to_bytes();
399 tx.execute(QUERY, params![serialized_address, account_id.to_bytes(),])
400 .into_store_error()?;
401
402 Ok(())
403 }
404
405 pub(crate) fn remove_address(
407 conn: &mut Connection,
408 address: &Address,
409 ) -> Result<bool, StoreError> {
410 with_write_tx(conn, |tx| {
411 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
412 let count = tx.execute(DELETE_QUERY, params![address.to_bytes()]).into_store_error()?;
413
414 Ok(count > 0)
415 })
416 }
417
418 pub(crate) fn insert_account_code(
420 tx: &Transaction<'_>,
421 account_code: &AccountCode,
422 ) -> Result<(), StoreError> {
423 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
424 tx.execute(
425 QUERY,
426 params![account_code.commitment().to_bytes(), proto::encode(account_code)],
427 )
428 .into_store_error()?;
429 Ok(())
430 }
431
432 pub(crate) fn apply_account_patch(
436 tx: &Transaction<'_>,
437 smt_forest: &mut ScopedAccountForest<'_, '_>,
438 init_account_state: &AccountHeader,
439 final_account_state: &AccountHeader,
440 patch: &AccountPatch,
441 ) -> Result<(), StoreError> {
442 let account_id = final_account_state.id();
443
444 let stored_header = Self::require_latest_account_header(tx, account_id)?;
449 if stored_header.to_commitment() != init_account_state.to_commitment() {
450 return Err(StoreError::DatabaseError(format!(
451 "apply_account_patch: stored state {} for account {} does not match the patch's \
452 initial state {}",
453 stored_header.to_commitment(),
454 account_id,
455 init_account_state.to_commitment(),
456 )));
457 }
458
459 match patch.code().as_code() {
462 Some(code) => {
463 if code.commitment() != final_account_state.code_commitment() {
464 return Err(StoreError::DatabaseError(format!(
465 "apply_account_patch: patch code commitment {} for account {} does not \
466 match the final code commitment {}",
467 code.commitment(),
468 account_id,
469 final_account_state.code_commitment(),
470 )));
471 }
472 Self::insert_account_code(tx, code)?;
473 },
474 None => {
475 if init_account_state.code_commitment() != final_account_state.code_commitment() {
476 return Err(StoreError::DatabaseError(format!(
477 "apply_account_patch: patch for account {} changes the code commitment \
478 from {} to {} but does not contain the new code",
479 account_id,
480 init_account_state.code_commitment(),
481 final_account_state.code_commitment(),
482 )));
483 }
484 },
485 }
486
487 Self::replace_account_header(tx, final_account_state, init_account_state, None)?;
489
490 Self::apply_account_vault_patch(tx, account_id, final_account_state, patch.vault())?;
491
492 let mut update = AccountUpdate::new();
495 update.vault_patch(account_id, patch.vault(), final_account_state.vault_root());
496 update.storage_patch(account_id, patch.storage());
497
498 let revision = allocate_forest_revision(tx).into_store_error()?;
499 smt_forest.apply(revision, update)?;
500
501 Self::write_storage_patch(
502 tx,
503 smt_forest,
504 account_id,
505 final_account_state.nonce().as_canonical_u64(),
506 patch.storage(),
507 )?;
508 Self::verify_storage_commitment(tx, account_id, final_account_state.storage_commitment())?;
509
510 Ok(())
511 }
512
513 pub(crate) fn reconcile_account_forest(
518 tx: &Transaction<'_>,
519 smt_forest: &mut ScopedAccountForest<'_, '_>,
520 account_id: AccountId,
521 vault: &AssetVault,
522 storage: &AccountStorage,
523 ) -> Result<(), StoreError> {
524 let mut update = AccountUpdate::new();
525 update.full_state(account_id, vault.assets(), storage.slots().iter());
526
527 for slot_name in Self::query_map_slot_names(tx, account_id)? {
531 update.clear_map(account_id, &slot_name);
532 }
533
534 Self::apply_forest_update(tx, smt_forest, update)
535 }
536
537 fn reconcile_account_forest_from_tables(
544 tx: &Transaction<'_>,
545 smt_forest: &mut ScopedAccountForest<'_, '_>,
546 account_id: AccountId,
547 extra_map_slots: &[StorageSlotName],
548 ) -> Result<(), StoreError> {
549 let assets = query_vault_assets(tx, account_id)?;
550 let slots = query_storage_slots(tx, account_id, &AccountStorageFilter::All)?;
551
552 let mut update = AccountUpdate::new();
553 update.full_state(account_id, assets.into_iter(), slots.values());
554 for slot_name in extra_map_slots {
555 update.clear_map(account_id, slot_name);
556 }
557
558 Self::apply_forest_update(tx, smt_forest, update)
559 }
560
561 fn verify_storage_commitment(
566 tx: &Transaction<'_>,
567 account_id: AccountId,
568 expected: Word,
569 ) -> Result<(), StoreError> {
570 let mut slot_headers: Vec<StorageSlotHeader> = query_storage_values(tx, account_id)?
571 .into_iter()
572 .map(|(slot_name, (slot_type, value))| {
573 StorageSlotHeader::new(slot_name, slot_type, value)
574 })
575 .collect();
576 slot_headers.sort_by_key(StorageSlotHeader::id);
577
578 let actual = AccountStorageHeader::new(slot_headers)
579 .map_err(StoreError::AccountError)?
580 .to_commitment();
581 if actual != expected {
582 return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
583 expected_root: expected,
584 actual_root: actual,
585 }));
586 }
587
588 Ok(())
589 }
590
591 fn apply_forest_update(
593 tx: &Transaction<'_>,
594 smt_forest: &mut ScopedAccountForest<'_, '_>,
595 update: AccountUpdate,
596 ) -> Result<(), StoreError> {
597 let revision = allocate_forest_revision(tx).into_store_error()?;
598 smt_forest.apply(revision, update)
599 }
600
601 fn require_latest_account_header(
604 tx: &Transaction<'_>,
605 account_id: AccountId,
606 ) -> Result<AccountHeader, StoreError> {
607 query_latest_account_headers(tx, "id = ?", params![account_id.to_bytes()])?
608 .into_iter()
609 .next()
610 .map(|(header, ..)| header)
611 .ok_or(StoreError::AccountDataNotFound(account_id))
612 }
613
614 fn query_map_slot_names(
616 tx: &Transaction<'_>,
617 account_id: AccountId,
618 ) -> Result<Vec<StorageSlotName>, StoreError> {
619 let mut stmt = tx
620 .prepare(
621 "SELECT DISTINCT slot_name FROM latest_storage_map_entries WHERE account_id = ?",
622 )
623 .into_store_error()?;
624 let rows = stmt
625 .query_map(params![account_id.to_bytes()], |row| row.get::<_, String>(0))
626 .into_store_error()?;
627
628 rows.map(|row| {
629 StorageSlotName::new(row.into_store_error()?)
630 .map_err(|e| StoreError::ParsingError(e.to_string()))
631 })
632 .collect()
633 }
634
635 pub(crate) fn undo_account_state(
637 tx: &Transaction<'_>,
638 smt_forest: &mut ScopedAccountForest<'_, '_>,
639 discarded_states: &[(AccountId, Word)],
640 ) -> Result<(), StoreError> {
641 if discarded_states.is_empty() {
642 return Ok(());
643 }
644
645 let commitment_params =
646 blob_array(discarded_states.iter().map(|(_, commitment)| commitment));
647
648 let mut nonces_by_account: BTreeMap<Vec<u8>, BTreeSet<u64>> = BTreeMap::new();
652 for query in [
653 "SELECT id, nonce FROM latest_account_headers WHERE account_commitment IN rarray(?)",
654 "SELECT id, nonce FROM historical_account_headers WHERE account_commitment IN rarray(?)",
655 ] {
656 for row in tx
657 .prepare(query)
658 .into_store_error()?
659 .query_map(params![commitment_params.clone()], |row| {
660 let id: Vec<u8> = row.get("id")?;
661 let nonce: u64 = column_value_as_u64(row, "nonce")?;
662 Ok((id, nonce))
663 })
664 .into_store_error()?
665 {
666 let (id, nonce) = row.into_store_error()?;
667 nonces_by_account.entry(id).or_default().insert(nonce);
668 }
669 }
670
671 for (account_id_bytes, nonces) in &nonces_by_account {
674 let account_id = AccountId::read_from_bytes(account_id_bytes)?;
675 let stale_map_slots = Self::query_map_slot_names(tx, account_id)?;
676 Self::undo_account_nonces(tx, account_id_bytes, nonces)?;
677 Self::reconcile_account_forest_from_tables(
678 tx,
679 smt_forest,
680 account_id,
681 &stale_map_slots,
682 )?;
683 }
684
685 Ok(())
686 }
687
688 fn undo_account_nonces(
691 tx: &Transaction<'_>,
692 account_id_bytes: &[u8],
693 nonces: &BTreeSet<u64>,
694 ) -> Result<(), StoreError> {
695 for &nonce in nonces.iter().rev() {
699 let nonce_val = u64_to_value(nonce);
700 Self::restore_old_values_for_nonce(tx, account_id_bytes, &nonce_val)?;
701 }
702
703 let min_nonce_val = u64_to_value(*nonces.first().expect("nonces is not empty"));
706
707 let old_header_exists: bool = tx
708 .query_row(
709 "SELECT COUNT(*) FROM historical_account_headers \
710 WHERE id = ? AND replaced_at_nonce = ?",
711 params![account_id_bytes, &min_nonce_val],
712 |row| row.get::<_, i64>(0),
713 )
714 .into_store_error()?
715 > 0;
716
717 if old_header_exists {
718 tx.execute(
722 "INSERT OR REPLACE INTO latest_account_headers \
723 (id, account_commitment, code_commitment, storage_commitment, \
724 vault_root, nonce, account_seed, locked) \
725 SELECT id, account_commitment, code_commitment, storage_commitment, \
726 vault_root, nonce, account_seed, locked \
727 FROM historical_account_headers \
728 WHERE id = ? AND replaced_at_nonce = ?",
729 params![account_id_bytes, &min_nonce_val],
730 )
731 .into_store_error()?;
732 } else {
733 for table in [
735 "DELETE FROM latest_account_headers WHERE id = ?",
736 "DELETE FROM latest_account_storage WHERE account_id = ?",
737 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
738 "DELETE FROM latest_account_assets WHERE account_id = ?",
739 ] {
740 tx.execute(table, params![account_id_bytes]).into_store_error()?;
741 }
742 }
743
744 let nonce_params = int_array(nonces.iter().copied());
746 for table in [
747 "historical_account_storage",
748 "historical_storage_map_entries",
749 "historical_account_assets",
750 ] {
751 tx.execute(
752 &format!(
753 "DELETE FROM {table} WHERE account_id = ? AND replaced_at_nonce IN rarray(?)"
754 ),
755 params![account_id_bytes, nonce_params.clone()],
756 )
757 .into_store_error()?;
758 }
759 tx.execute(
760 "DELETE FROM historical_account_headers \
761 WHERE id = ? AND replaced_at_nonce IN rarray(?)",
762 params![account_id_bytes, nonce_params],
763 )
764 .into_store_error()?;
765
766 Ok(())
767 }
768
769 fn restore_old_values_for_nonce(
772 tx: &Transaction<'_>,
773 account_id_bytes: &[u8],
774 nonce_val: &rusqlite::types::Value,
775 ) -> Result<(), StoreError> {
776 tx.execute(
778 "INSERT OR REPLACE INTO latest_account_storage \
779 (account_id, slot_name, slot_value, slot_type) \
780 SELECT account_id, slot_name, old_slot_value, slot_type \
781 FROM historical_account_storage \
782 WHERE account_id = ? AND replaced_at_nonce = ? AND old_slot_value IS NOT NULL",
783 params![account_id_bytes, nonce_val],
784 )
785 .into_store_error()?;
786
787 tx.execute(
789 "DELETE FROM latest_account_storage \
790 WHERE account_id = ?1 AND slot_name IN (\
791 SELECT slot_name FROM historical_account_storage \
792 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_slot_value IS NULL\
793 )",
794 params![account_id_bytes, nonce_val],
795 )
796 .into_store_error()?;
797
798 tx.execute(
800 "INSERT OR REPLACE INTO latest_storage_map_entries \
801 (account_id, slot_name, key, value) \
802 SELECT account_id, slot_name, key, old_value \
803 FROM historical_storage_map_entries \
804 WHERE account_id = ? AND replaced_at_nonce = ? AND old_value IS NOT NULL",
805 params![account_id_bytes, nonce_val],
806 )
807 .into_store_error()?;
808
809 tx.execute(
811 "DELETE FROM latest_storage_map_entries \
812 WHERE account_id = ?1 AND EXISTS (\
813 SELECT 1 FROM historical_storage_map_entries h \
814 WHERE h.account_id = latest_storage_map_entries.account_id \
815 AND h.slot_name = latest_storage_map_entries.slot_name \
816 AND h.key = latest_storage_map_entries.key \
817 AND h.replaced_at_nonce = ?2 AND h.old_value IS NULL\
818 )",
819 params![account_id_bytes, nonce_val],
820 )
821 .into_store_error()?;
822
823 tx.execute(
825 "INSERT OR REPLACE INTO latest_account_assets \
826 (account_id, asset_id, asset) \
827 SELECT account_id, asset_id, old_asset \
828 FROM historical_account_assets \
829 WHERE account_id = ? AND replaced_at_nonce = ? AND old_asset IS NOT NULL",
830 params![account_id_bytes, nonce_val],
831 )
832 .into_store_error()?;
833
834 tx.execute(
836 "DELETE FROM latest_account_assets \
837 WHERE account_id = ?1 AND asset_id IN (\
838 SELECT asset_id FROM historical_account_assets \
839 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_asset IS NULL\
840 )",
841 params![account_id_bytes, nonce_val],
842 )
843 .into_store_error()?;
844
845 Ok(())
846 }
847
848 pub(crate) fn update_account_state(
853 tx: &Transaction<'_>,
854 smt_forest: &mut ScopedAccountForest<'_, '_>,
855 new_account_state: &Account,
856 ) -> Result<(), StoreError> {
857 let account_id = new_account_state.id();
858 let account_id_bytes = account_id.to_bytes();
859
860 let old_header = Self::require_latest_account_header(tx, account_id)?;
863
864 if new_account_state.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
865 return Err(StoreError::DatabaseError(format!(
866 "update_account_state: new nonce {} is less than old nonce {} for account {}",
867 new_account_state.nonce().as_canonical_u64(),
868 old_header.nonce().as_canonical_u64(),
869 account_id,
870 )));
871 }
872
873 let nonce_val = u64_to_value(new_account_state.nonce().as_canonical_u64());
874
875 Self::reconcile_account_forest(
877 tx,
878 smt_forest,
879 account_id,
880 new_account_state.vault(),
881 new_account_state.storage(),
882 )?;
883
884 tx.execute(
886 "INSERT OR REPLACE INTO historical_account_storage \
887 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
888 SELECT account_id, ?, slot_name, slot_value, slot_type \
889 FROM latest_account_storage WHERE account_id = ?",
890 params![&nonce_val, &account_id_bytes],
891 )
892 .into_store_error()?;
893 tx.execute(
894 "INSERT OR REPLACE INTO historical_storage_map_entries \
895 (account_id, replaced_at_nonce, slot_name, key, old_value) \
896 SELECT account_id, ?, slot_name, key, value \
897 FROM latest_storage_map_entries WHERE account_id = ?",
898 params![&nonce_val, &account_id_bytes],
899 )
900 .into_store_error()?;
901 tx.execute(
902 "INSERT OR REPLACE INTO historical_account_assets \
903 (account_id, replaced_at_nonce, asset_id, old_asset) \
904 SELECT account_id, ?, asset_id, asset \
905 FROM latest_account_assets WHERE account_id = ?",
906 params![&nonce_val, &account_id_bytes],
907 )
908 .into_store_error()?;
909
910 tx.execute(
912 "DELETE FROM latest_account_storage WHERE account_id = ?",
913 params![&account_id_bytes],
914 )
915 .into_store_error()?;
916 tx.execute(
917 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
918 params![&account_id_bytes],
919 )
920 .into_store_error()?;
921 tx.execute(
922 "DELETE FROM latest_account_assets WHERE account_id = ?",
923 params![&account_id_bytes],
924 )
925 .into_store_error()?;
926
927 Self::insert_storage_slots(tx, account_id, new_account_state.storage().slots().iter())?;
929 Self::insert_assets(tx, account_id, new_account_state.vault().assets())?;
930
931 tx.execute(
934 "INSERT OR IGNORE INTO historical_account_storage \
935 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
936 SELECT account_id, ?, slot_name, NULL, slot_type \
937 FROM latest_account_storage WHERE account_id = ?",
938 params![&nonce_val, &account_id_bytes],
939 )
940 .into_store_error()?;
941 tx.execute(
942 "INSERT OR IGNORE INTO historical_storage_map_entries \
943 (account_id, replaced_at_nonce, slot_name, key, old_value) \
944 SELECT account_id, ?, slot_name, key, NULL \
945 FROM latest_storage_map_entries WHERE account_id = ?",
946 params![&nonce_val, &account_id_bytes],
947 )
948 .into_store_error()?;
949 tx.execute(
950 "INSERT OR IGNORE INTO historical_account_assets \
951 (account_id, replaced_at_nonce, asset_id, old_asset) \
952 SELECT account_id, ?, asset_id, NULL \
953 FROM latest_account_assets WHERE account_id = ?",
954 params![&nonce_val, &account_id_bytes],
955 )
956 .into_store_error()?;
957
958 Self::insert_account_code(tx, new_account_state.code())?;
961
962 let new_seed = new_account_state.seed().filter(|_| new_account_state.is_new());
965 Self::replace_account_header(tx, &new_account_state.into(), &old_header, new_seed)?;
966
967 Ok(())
968 }
969
970 pub(crate) fn apply_sync_account_patch(
972 tx: &Transaction<'_>,
973 smt_forest: &mut ScopedAccountForest<'_, '_>,
974 new_header: &AccountHeader,
975 patch: &AccountPatch,
976 ) -> Result<(), StoreError> {
977 let account_id = new_header.id();
978
979 let init_header = Self::require_latest_account_header(tx, account_id)?;
981
982 if new_header.nonce().as_canonical_u64() <= init_header.nonce().as_canonical_u64() {
983 return Err(StoreError::DatabaseError(format!(
984 "apply_sync_account_patch: new nonce {} is not greater than local nonce {} for account {}",
985 new_header.nonce().as_canonical_u64(),
986 init_header.nonce().as_canonical_u64(),
987 account_id,
988 )));
989 }
990
991 Self::apply_account_patch(tx, smt_forest, &init_header, new_header, patch)
994 }
995
996 pub(crate) fn lock_account_on_unexpected_commitment(
999 tx: &Transaction<'_>,
1000 account_id: &AccountId,
1001 mismatched_digest: &Word,
1002 ) -> Result<(), StoreError> {
1003 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)";
1007 let account_id_bytes = account_id.to_bytes();
1008 let digest_bytes = mismatched_digest.to_bytes();
1009 let params = named_params! {
1010 ":account_id": account_id_bytes,
1011 ":digest": digest_bytes
1012 };
1013
1014 let query = format!("UPDATE latest_account_headers SET locked = true {LOCK_CONDITION}");
1015 tx.execute(&query, params).into_store_error()?;
1016
1017 let query = format!("UPDATE historical_account_headers SET locked = true {LOCK_CONDITION}");
1019 tx.execute(&query, params).into_store_error()?;
1020
1021 Ok(())
1022 }
1023
1024 fn insert_new_account_header(
1033 tx: &Transaction<'_>,
1034 new_header: &AccountHeader,
1035 account_seed: Option<Word>,
1036 watched: bool,
1037 ) -> Result<(), StoreError> {
1038 let id = new_header.id().to_bytes();
1039 let code_commitment = new_header.code_commitment().to_bytes();
1040 let storage_commitment = new_header.storage_commitment().to_bytes();
1041 let vault_root = new_header.vault_root().to_bytes();
1042 let nonce = u64_to_value(new_header.nonce().as_canonical_u64());
1043 let commitment = new_header.to_commitment().to_bytes();
1044 let account_seed = account_seed.map(|seed| seed.to_bytes());
1045
1046 const LATEST_QUERY: &str = insert_sql!(
1047 latest_account_headers {
1048 id,
1049 code_commitment,
1050 storage_commitment,
1051 vault_root,
1052 nonce,
1053 account_seed,
1054 account_commitment,
1055 locked,
1056 watched
1057 } | REPLACE
1058 );
1059
1060 tx.execute(
1061 LATEST_QUERY,
1062 params![
1063 id,
1064 code_commitment,
1065 storage_commitment,
1066 vault_root,
1067 nonce,
1068 account_seed,
1069 commitment,
1070 false,
1071 watched,
1072 ],
1073 )
1074 .into_store_error()?;
1075
1076 Ok(())
1077 }
1078
1079 fn replace_account_header(
1087 tx: &Transaction<'_>,
1088 new_header: &AccountHeader,
1089 old_header: &AccountHeader,
1090 new_seed: Option<Word>,
1091 ) -> Result<(), StoreError> {
1092 if new_header.id() != old_header.id() {
1093 return Err(StoreError::DatabaseError(format!(
1094 "replace_account_header: account id mismatch (new: {}, old: {})",
1095 new_header.id(),
1096 old_header.id(),
1097 )));
1098 }
1099 if new_header.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
1100 return Err(StoreError::DatabaseError(format!(
1101 "replace_account_header: new nonce {} is less than old nonce {} for account {}",
1102 new_header.nonce().as_canonical_u64(),
1103 old_header.nonce().as_canonical_u64(),
1104 new_header.id(),
1105 )));
1106 }
1107
1108 let id_bytes = new_header.id().to_bytes();
1109
1110 let (old_seed, old_locked, old_watched): (Option<Vec<u8>>, bool, bool) = tx
1114 .query_row(
1115 "SELECT account_seed, locked, watched FROM latest_account_headers WHERE id = ?",
1116 params![&id_bytes],
1117 |row| Ok((row.get("account_seed")?, row.get("locked")?, row.get("watched")?)),
1118 )
1119 .optional()
1120 .into_store_error()?
1121 .unwrap_or((None, false, false));
1122
1123 let old_id = old_header.id().to_bytes();
1125 let old_code_commitment = old_header.code_commitment().to_bytes();
1126 let old_storage_commitment = old_header.storage_commitment().to_bytes();
1127 let old_vault_root = old_header.vault_root().to_bytes();
1128 let old_nonce = u64_to_value(old_header.nonce().as_canonical_u64());
1129 let old_commitment = old_header.to_commitment().to_bytes();
1130 let replaced_at_nonce = u64_to_value(new_header.nonce().as_canonical_u64());
1131
1132 const HISTORICAL_QUERY: &str = insert_sql!(
1133 historical_account_headers {
1134 id,
1135 code_commitment,
1136 storage_commitment,
1137 vault_root,
1138 nonce,
1139 account_seed,
1140 account_commitment,
1141 locked,
1142 replaced_at_nonce
1143 } | REPLACE
1144 );
1145
1146 tx.execute(
1147 HISTORICAL_QUERY,
1148 params![
1149 old_id,
1150 old_code_commitment,
1151 old_storage_commitment,
1152 old_vault_root,
1153 old_nonce,
1154 old_seed,
1155 old_commitment,
1156 old_locked,
1157 replaced_at_nonce,
1158 ],
1159 )
1160 .into_store_error()?;
1161
1162 Self::insert_new_account_header(tx, new_header, new_seed, old_watched)
1164 }
1165
1166 pub(crate) fn prune_account_history(
1172 conn: &mut Connection,
1173 account_id: AccountId,
1174 up_to_nonce: Felt,
1175 ) -> Result<usize, StoreError> {
1176 with_write_tx(conn, |tx| {
1177 let account_id_bytes = account_id.to_bytes();
1178 let boundary_val = u64_to_value(up_to_nonce.as_canonical_u64());
1179 let mut total_deleted: usize = 0;
1180
1181 let candidate_code_commitments: Vec<Vec<u8>> = {
1183 let mut stmt = tx
1184 .prepare(
1185 "SELECT DISTINCT code_commitment FROM historical_account_headers \
1186 WHERE id = ? AND replaced_at_nonce <= ?",
1187 )
1188 .into_store_error()?;
1189 let rows = stmt
1190 .query_map(params![&account_id_bytes, &boundary_val], |row| row.get(0))
1191 .into_store_error()?;
1192 rows.collect::<Result<Vec<Vec<u8>>, _>>().into_store_error()?
1193 };
1194
1195 for (table, account_column) in [
1197 ("historical_account_headers", "id"),
1198 ("historical_account_storage", "account_id"),
1199 ("historical_storage_map_entries", "account_id"),
1200 ("historical_account_assets", "account_id"),
1201 ] {
1202 let query = format!(
1203 "DELETE FROM {table} WHERE {account_column} = ? AND replaced_at_nonce <= ?"
1204 );
1205 total_deleted += tx
1206 .execute(&query, params![&account_id_bytes, &boundary_val])
1207 .into_store_error()?;
1208 }
1209
1210 for commitment in &candidate_code_commitments {
1213 let still_referenced: bool = tx
1214 .query_row(
1215 "SELECT EXISTS(
1216 SELECT 1 FROM latest_account_headers WHERE code_commitment = ?1
1217 UNION ALL
1218 SELECT 1 FROM historical_account_headers WHERE code_commitment = ?1
1219 UNION ALL
1220 SELECT 1 FROM foreign_account_code WHERE code_commitment = ?1
1221 )",
1222 params![commitment],
1223 |row| row.get(0),
1224 )
1225 .into_store_error()?;
1226
1227 if !still_referenced {
1228 total_deleted += tx
1229 .execute(
1230 "DELETE FROM account_code WHERE commitment = ?",
1231 params![commitment],
1232 )
1233 .into_store_error()?;
1234 }
1235 }
1236
1237 Ok(total_deleted)
1238 })
1239 }
1240}