1use std::collections::BTreeMap;
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_account_addresses(
300 conn: &mut Connection,
301 account_id: AccountId,
302 ) -> Result<Vec<Address>, StoreError> {
303 query_account_addresses(conn, account_id)
304 }
305
306 pub(crate) fn get_account_code_by_id(
308 conn: &mut Connection,
309 account_id: AccountId,
310 ) -> Result<Option<AccountCode>, StoreError> {
311 let Some((header, ..)) =
312 query_latest_account_headers(conn, "id = ?", params![account_id.to_bytes()])?
313 .into_iter()
314 .next()
315 else {
316 return Ok(None);
317 };
318
319 query_account_code(conn, header.code_commitment())
320 }
321
322 pub(crate) fn insert_account(
326 conn: &mut Connection,
327 account: &Account,
328 initial_address: &Address,
329 client_account_type: ClientAccountType,
330 ) -> Result<(), StoreError> {
331 let db_tx = conn
332 .transaction_with_behavior(TransactionBehavior::Immediate)
333 .into_store_error()?;
334 {
335 let mut smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
336 Self::insert_account_code(&db_tx, account.code())?;
337
338 let account_id = account.id();
339 Self::insert_storage_slots(&db_tx, account_id, account.storage().slots().iter())?;
340 Self::insert_assets(&db_tx, account_id, account.vault().assets())?;
341 let watched = matches!(client_account_type, ClientAccountType::Watched);
342 Self::insert_new_account_header(&db_tx, &account.into(), account.seed(), watched)?;
343 Self::insert_address(&db_tx, initial_address, account.id())?;
344
345 Self::reconcile_account_forest(
346 &db_tx,
347 &mut smt_forest,
348 account_id,
349 account.vault(),
350 account.storage(),
351 )?;
352 }
353 db_tx.commit().into_store_error()
354 }
355
356 pub(crate) fn update_account(
357 conn: &mut Connection,
358 new_account_state: &Account,
359 ) -> Result<(), StoreError> {
360 const QUERY: &str = "SELECT id FROM latest_account_headers WHERE id = ?";
361 if conn
362 .prepare(QUERY)
363 .into_store_error()?
364 .query_map(params![new_account_state.id().to_bytes()], |row| row.get(0))
365 .into_store_error()?
366 .map(|result| {
367 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
368 |id: Vec<u8>| {
369 AccountId::read_from_bytes(&id)
370 .map_err(StoreError::DataDeserializationError)
371 },
372 )
373 })
374 .next()
375 .is_none()
376 {
377 return Err(StoreError::AccountDataNotFound(new_account_state.id()));
378 }
379
380 let db_tx = conn
381 .transaction_with_behavior(TransactionBehavior::Immediate)
382 .into_store_error()?;
383 {
384 let mut smt_forest = ScopedAccountForest::new(SqliteForestBackend::new(&db_tx))?;
385 Self::update_account_state(&db_tx, &mut smt_forest, new_account_state)?;
386 }
387 db_tx.commit().into_store_error()
388 }
389
390 pub fn upsert_foreign_account_code(
391 conn: &mut Connection,
392 account_id: AccountId,
393 code: &AccountCode,
394 ) -> Result<(), StoreError> {
395 let tx = conn.transaction().into_store_error()?;
396
397 Self::insert_account_code(&tx, code)?;
398
399 const QUERY: &str =
400 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
401
402 tx.execute(QUERY, params![account_id.to_bytes(), code.commitment().to_bytes()])
403 .into_store_error()?;
404
405 Self::insert_account_code(&tx, code)?;
406 tx.commit().into_store_error()
407 }
408
409 pub(crate) fn insert_address(
410 tx: &Transaction<'_>,
411 address: &Address,
412 account_id: AccountId,
413 ) -> Result<(), StoreError> {
414 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
415 let serialized_address = address.to_bytes();
416 tx.execute(QUERY, params![serialized_address, account_id.to_bytes(),])
417 .into_store_error()?;
418
419 Ok(())
420 }
421
422 pub(crate) fn remove_address(
424 conn: &mut Connection,
425 address: &Address,
426 ) -> Result<bool, StoreError> {
427 let tx = conn.transaction().into_store_error()?;
428 let serialized_address = address.to_bytes();
429 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
430 let count = tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
431
432 tx.commit().into_store_error()?;
433
434 Ok(count > 0)
435 }
436
437 pub(crate) fn insert_account_code(
439 tx: &Transaction<'_>,
440 account_code: &AccountCode,
441 ) -> Result<(), StoreError> {
442 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
443 tx.execute(QUERY, params![account_code.commitment().to_bytes(), account_code.to_bytes()])
444 .into_store_error()?;
445 Ok(())
446 }
447
448 pub(crate) fn apply_account_patch(
452 tx: &Transaction<'_>,
453 smt_forest: &mut ScopedAccountForest<'_, '_>,
454 init_account_state: &AccountHeader,
455 final_account_state: &AccountHeader,
456 patch: &AccountPatch,
457 ) -> Result<(), StoreError> {
458 let account_id = final_account_state.id();
459
460 let stored_header = Self::require_latest_account_header(tx, account_id)?;
465 if stored_header.to_commitment() != init_account_state.to_commitment() {
466 return Err(StoreError::DatabaseError(format!(
467 "apply_account_patch: stored state {} for account {} does not match the patch's \
468 initial state {}",
469 stored_header.to_commitment(),
470 account_id,
471 init_account_state.to_commitment(),
472 )));
473 }
474
475 Self::replace_account_header(tx, final_account_state, init_account_state)?;
477
478 Self::apply_account_vault_patch(tx, account_id, final_account_state, patch.vault())?;
479
480 let mut update = AccountUpdate::new();
483 update.vault_patch(account_id, patch.vault(), final_account_state.vault_root());
484 update.storage_patch(account_id, patch.storage());
485
486 let revision = allocate_forest_revision(tx).into_store_error()?;
487 smt_forest.apply(revision, update)?;
488
489 Self::write_storage_patch(
490 tx,
491 smt_forest,
492 account_id,
493 final_account_state.nonce().as_canonical_u64(),
494 patch.storage(),
495 )?;
496 Self::verify_storage_commitment(tx, account_id, final_account_state.storage_commitment())?;
497
498 Ok(())
499 }
500
501 pub(crate) fn reconcile_account_forest(
506 tx: &Transaction<'_>,
507 smt_forest: &mut ScopedAccountForest<'_, '_>,
508 account_id: AccountId,
509 vault: &AssetVault,
510 storage: &AccountStorage,
511 ) -> Result<(), StoreError> {
512 let mut update = AccountUpdate::new();
513 update.full_state(account_id, vault.assets(), storage.slots().iter());
514
515 for slot_name in Self::query_map_slot_names(tx, account_id)? {
519 update.clear_map(account_id, &slot_name);
520 }
521
522 Self::apply_forest_update(tx, smt_forest, update)
523 }
524
525 fn reconcile_account_forest_from_tables(
532 tx: &Transaction<'_>,
533 smt_forest: &mut ScopedAccountForest<'_, '_>,
534 account_id: AccountId,
535 extra_map_slots: &[StorageSlotName],
536 ) -> Result<(), StoreError> {
537 let assets = query_vault_assets(tx, account_id)?;
538 let slots = query_storage_slots(tx, account_id, &AccountStorageFilter::All)?;
539
540 let mut update = AccountUpdate::new();
541 update.full_state(account_id, assets.into_iter(), slots.values());
542 for slot_name in extra_map_slots {
543 update.clear_map(account_id, slot_name);
544 }
545
546 Self::apply_forest_update(tx, smt_forest, update)
547 }
548
549 fn verify_storage_commitment(
554 tx: &Transaction<'_>,
555 account_id: AccountId,
556 expected: Word,
557 ) -> Result<(), StoreError> {
558 let mut slot_headers: Vec<StorageSlotHeader> = query_storage_values(tx, account_id)?
559 .into_iter()
560 .map(|(slot_name, (slot_type, value))| {
561 StorageSlotHeader::new(slot_name, slot_type, value)
562 })
563 .collect();
564 slot_headers.sort_by_key(StorageSlotHeader::id);
565
566 let actual = AccountStorageHeader::new(slot_headers)
567 .map_err(StoreError::AccountError)?
568 .to_commitment();
569 if actual != expected {
570 return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
571 expected_root: expected,
572 actual_root: actual,
573 }));
574 }
575
576 Ok(())
577 }
578
579 fn apply_forest_update(
581 tx: &Transaction<'_>,
582 smt_forest: &mut ScopedAccountForest<'_, '_>,
583 update: AccountUpdate,
584 ) -> Result<(), StoreError> {
585 let revision = allocate_forest_revision(tx).into_store_error()?;
586 smt_forest.apply(revision, update)
587 }
588
589 fn require_latest_account_header(
592 tx: &Transaction<'_>,
593 account_id: AccountId,
594 ) -> Result<AccountHeader, StoreError> {
595 query_latest_account_headers(tx, "id = ?", params![account_id.to_bytes()])?
596 .into_iter()
597 .next()
598 .map(|(header, ..)| header)
599 .ok_or(StoreError::AccountDataNotFound(account_id))
600 }
601
602 fn query_map_slot_names(
604 tx: &Transaction<'_>,
605 account_id: AccountId,
606 ) -> Result<Vec<StorageSlotName>, StoreError> {
607 let mut stmt = tx
608 .prepare(
609 "SELECT DISTINCT slot_name FROM latest_storage_map_entries WHERE account_id = ?",
610 )
611 .into_store_error()?;
612 let rows = stmt
613 .query_map(params![account_id.to_bytes()], |row| row.get::<_, String>(0))
614 .into_store_error()?;
615
616 let mut names = Vec::new();
617 for row in rows {
618 names.push(
619 StorageSlotName::new(row.into_store_error()?)
620 .map_err(|e| StoreError::ParsingError(e.to_string()))?,
621 );
622 }
623 Ok(names)
624 }
625
626 pub(crate) fn undo_account_state(
628 tx: &Transaction<'_>,
629 smt_forest: &mut ScopedAccountForest<'_, '_>,
630 discarded_states: &[(AccountId, Word)],
631 ) -> Result<(), StoreError> {
632 if discarded_states.is_empty() {
633 return Ok(());
634 }
635
636 let commitment_params = Rc::new(
637 discarded_states
638 .iter()
639 .map(|(_, commitment)| Value::Blob(commitment.to_bytes()))
640 .collect::<Vec<_>>(),
641 );
642
643 let mut id_nonce_pairs: Vec<(Vec<u8>, u64)> = Vec::new();
646 for query in [
647 "SELECT id, nonce FROM latest_account_headers WHERE account_commitment IN rarray(?)",
648 "SELECT id, nonce FROM historical_account_headers WHERE account_commitment IN rarray(?)",
649 ] {
650 id_nonce_pairs.extend(
651 tx.prepare(query)
652 .into_store_error()?
653 .query_map(params![commitment_params.clone()], |row| {
654 let id: Vec<u8> = row.get(0)?;
655 let nonce: u64 = column_value_as_u64(row, 1)?;
656 Ok((id, nonce))
657 })
658 .into_store_error()?
659 .filter_map(Result::ok),
660 );
661 }
662
663 let mut nonces_by_account: BTreeMap<Vec<u8>, Vec<u64>> = BTreeMap::new();
668 for (id, nonce) in &id_nonce_pairs {
669 nonces_by_account.entry(id.clone()).or_default().push(*nonce);
670 }
671 for nonces in nonces_by_account.values_mut() {
672 nonces.sort_unstable();
673 nonces.dedup();
674 nonces.reverse();
675 }
676
677 let mut pre_undo_map_slots: BTreeMap<Vec<u8>, Vec<StorageSlotName>> = BTreeMap::new();
679 for account_id_bytes in nonces_by_account.keys() {
680 let account_id = AccountId::read_from_bytes(account_id_bytes)?;
681 pre_undo_map_slots
682 .insert(account_id_bytes.clone(), Self::query_map_slot_names(tx, account_id)?);
683 }
684
685 for (account_id_bytes, nonces) in &nonces_by_account {
687 Self::undo_account_nonces(tx, account_id_bytes, nonces)?;
688 }
689
690 for account_id_bytes in nonces_by_account.keys() {
692 let account_id = AccountId::read_from_bytes(account_id_bytes)?;
693 let stale_slots: &[StorageSlotName] =
694 pre_undo_map_slots.get(account_id_bytes).map_or(&[], Vec::as_slice);
695 Self::reconcile_account_forest_from_tables(tx, smt_forest, account_id, stale_slots)?;
696 }
697
698 Ok(())
699 }
700
701 fn undo_account_nonces(
704 tx: &Transaction<'_>,
705 account_id_bytes: &[u8],
706 nonces: &[u64],
707 ) -> Result<(), StoreError> {
708 for &nonce in nonces {
710 let nonce_val = u64_to_value(nonce);
711 Self::restore_old_values_for_nonce(tx, account_id_bytes, &nonce_val)?;
712 }
713
714 let min_nonce = *nonces.last().unwrap();
719 let min_nonce_val = u64_to_value(min_nonce);
720
721 let old_header_exists: bool = tx
722 .query_row(
723 "SELECT COUNT(*) FROM historical_account_headers \
724 WHERE id = ? AND replaced_at_nonce = ?",
725 params![account_id_bytes, &min_nonce_val],
726 |row| row.get::<_, i64>(0),
727 )
728 .into_store_error()?
729 > 0;
730
731 if old_header_exists {
732 tx.execute(
736 "INSERT OR REPLACE INTO latest_account_headers \
737 (id, account_commitment, code_commitment, storage_commitment, \
738 vault_root, nonce, account_seed, locked) \
739 SELECT id, account_commitment, code_commitment, storage_commitment, \
740 vault_root, nonce, account_seed, locked \
741 FROM historical_account_headers \
742 WHERE id = ? AND replaced_at_nonce = ?",
743 params![account_id_bytes, &min_nonce_val],
744 )
745 .into_store_error()?;
746 } else {
747 for table in [
749 "DELETE FROM latest_account_headers WHERE id = ?",
750 "DELETE FROM latest_account_storage WHERE account_id = ?",
751 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
752 "DELETE FROM latest_account_assets WHERE account_id = ?",
753 ] {
754 tx.execute(table, params![account_id_bytes]).into_store_error()?;
755 }
756 }
757
758 let nonce_params = Rc::new(nonces.iter().map(|n| u64_to_value(*n)).collect::<Vec<_>>());
760 for table in [
761 "historical_account_storage",
762 "historical_storage_map_entries",
763 "historical_account_assets",
764 ] {
765 tx.execute(
766 &format!(
767 "DELETE FROM {table} WHERE account_id = ? AND replaced_at_nonce IN rarray(?)"
768 ),
769 params![account_id_bytes, nonce_params.clone()],
770 )
771 .into_store_error()?;
772 }
773 tx.execute(
774 "DELETE FROM historical_account_headers \
775 WHERE id = ? AND replaced_at_nonce IN rarray(?)",
776 params![account_id_bytes, nonce_params],
777 )
778 .into_store_error()?;
779
780 Ok(())
781 }
782
783 fn restore_old_values_for_nonce(
786 tx: &Transaction<'_>,
787 account_id_bytes: &[u8],
788 nonce_val: &rusqlite::types::Value,
789 ) -> Result<(), StoreError> {
790 tx.execute(
792 "INSERT OR REPLACE INTO latest_account_storage \
793 (account_id, slot_name, slot_value, slot_type) \
794 SELECT account_id, slot_name, old_slot_value, slot_type \
795 FROM historical_account_storage \
796 WHERE account_id = ? AND replaced_at_nonce = ? AND old_slot_value IS NOT NULL",
797 params![account_id_bytes, nonce_val],
798 )
799 .into_store_error()?;
800
801 tx.execute(
803 "DELETE FROM latest_account_storage \
804 WHERE account_id = ?1 AND slot_name IN (\
805 SELECT slot_name FROM historical_account_storage \
806 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_slot_value IS NULL\
807 )",
808 params![account_id_bytes, nonce_val],
809 )
810 .into_store_error()?;
811
812 tx.execute(
814 "INSERT OR REPLACE INTO latest_storage_map_entries \
815 (account_id, slot_name, key, value) \
816 SELECT account_id, slot_name, key, old_value \
817 FROM historical_storage_map_entries \
818 WHERE account_id = ? AND replaced_at_nonce = ? AND old_value IS NOT NULL",
819 params![account_id_bytes, nonce_val],
820 )
821 .into_store_error()?;
822
823 tx.execute(
825 "DELETE FROM latest_storage_map_entries \
826 WHERE account_id = ?1 AND EXISTS (\
827 SELECT 1 FROM historical_storage_map_entries h \
828 WHERE h.account_id = latest_storage_map_entries.account_id \
829 AND h.slot_name = latest_storage_map_entries.slot_name \
830 AND h.key = latest_storage_map_entries.key \
831 AND h.replaced_at_nonce = ?2 AND h.old_value IS NULL\
832 )",
833 params![account_id_bytes, nonce_val],
834 )
835 .into_store_error()?;
836
837 tx.execute(
839 "INSERT OR REPLACE INTO latest_account_assets \
840 (account_id, asset_id, asset) \
841 SELECT account_id, asset_id, old_asset \
842 FROM historical_account_assets \
843 WHERE account_id = ? AND replaced_at_nonce = ? AND old_asset IS NOT NULL",
844 params![account_id_bytes, nonce_val],
845 )
846 .into_store_error()?;
847
848 tx.execute(
850 "DELETE FROM latest_account_assets \
851 WHERE account_id = ?1 AND asset_id IN (\
852 SELECT asset_id FROM historical_account_assets \
853 WHERE account_id = ?1 AND replaced_at_nonce = ?2 AND old_asset IS NULL\
854 )",
855 params![account_id_bytes, nonce_val],
856 )
857 .into_store_error()?;
858
859 Ok(())
860 }
861
862 pub(crate) fn update_account_state(
867 tx: &Transaction<'_>,
868 smt_forest: &mut ScopedAccountForest<'_, '_>,
869 new_account_state: &Account,
870 ) -> Result<(), StoreError> {
871 let account_id = new_account_state.id();
872 let account_id_bytes = account_id.to_bytes();
873
874 let old_header = query_latest_account_headers(tx, "id = ?", params![&account_id_bytes])?
877 .into_iter()
878 .next()
879 .map(|(header, ..)| header)
880 .ok_or(StoreError::AccountDataNotFound(account_id))?;
881
882 if new_account_state.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
883 return Err(StoreError::DatabaseError(format!(
884 "update_account_state: new nonce {} is less than old nonce {} for account {}",
885 new_account_state.nonce().as_canonical_u64(),
886 old_header.nonce().as_canonical_u64(),
887 account_id,
888 )));
889 }
890
891 let nonce_val = u64_to_value(new_account_state.nonce().as_canonical_u64());
892
893 Self::reconcile_account_forest(
895 tx,
896 smt_forest,
897 account_id,
898 new_account_state.vault(),
899 new_account_state.storage(),
900 )?;
901
902 tx.execute(
904 "INSERT OR REPLACE INTO historical_account_storage \
905 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
906 SELECT account_id, ?, slot_name, slot_value, slot_type \
907 FROM latest_account_storage WHERE account_id = ?",
908 params![&nonce_val, &account_id_bytes],
909 )
910 .into_store_error()?;
911 tx.execute(
912 "INSERT OR REPLACE INTO historical_storage_map_entries \
913 (account_id, replaced_at_nonce, slot_name, key, old_value) \
914 SELECT account_id, ?, slot_name, key, value \
915 FROM latest_storage_map_entries WHERE account_id = ?",
916 params![&nonce_val, &account_id_bytes],
917 )
918 .into_store_error()?;
919 tx.execute(
920 "INSERT OR REPLACE INTO historical_account_assets \
921 (account_id, replaced_at_nonce, asset_id, old_asset) \
922 SELECT account_id, ?, asset_id, asset \
923 FROM latest_account_assets WHERE account_id = ?",
924 params![&nonce_val, &account_id_bytes],
925 )
926 .into_store_error()?;
927
928 tx.execute(
930 "DELETE FROM latest_account_storage WHERE account_id = ?",
931 params![&account_id_bytes],
932 )
933 .into_store_error()?;
934 tx.execute(
935 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
936 params![&account_id_bytes],
937 )
938 .into_store_error()?;
939 tx.execute(
940 "DELETE FROM latest_account_assets WHERE account_id = ?",
941 params![&account_id_bytes],
942 )
943 .into_store_error()?;
944
945 Self::insert_storage_slots(tx, account_id, new_account_state.storage().slots().iter())?;
947 Self::insert_assets(tx, account_id, new_account_state.vault().assets())?;
948
949 tx.execute(
952 "INSERT OR IGNORE INTO historical_account_storage \
953 (account_id, replaced_at_nonce, slot_name, old_slot_value, slot_type) \
954 SELECT account_id, ?, slot_name, NULL, slot_type \
955 FROM latest_account_storage WHERE account_id = ?",
956 params![&nonce_val, &account_id_bytes],
957 )
958 .into_store_error()?;
959 tx.execute(
960 "INSERT OR IGNORE INTO historical_storage_map_entries \
961 (account_id, replaced_at_nonce, slot_name, key, old_value) \
962 SELECT account_id, ?, slot_name, key, NULL \
963 FROM latest_storage_map_entries WHERE account_id = ?",
964 params![&nonce_val, &account_id_bytes],
965 )
966 .into_store_error()?;
967 tx.execute(
968 "INSERT OR IGNORE INTO historical_account_assets \
969 (account_id, replaced_at_nonce, asset_id, old_asset) \
970 SELECT account_id, ?, asset_id, NULL \
971 FROM latest_account_assets WHERE account_id = ?",
972 params![&nonce_val, &account_id_bytes],
973 )
974 .into_store_error()?;
975
976 Self::replace_account_header(tx, &new_account_state.into(), &old_header)?;
978
979 Ok(())
980 }
981
982 pub(crate) fn apply_sync_account_patch(
984 tx: &Transaction<'_>,
985 smt_forest: &mut ScopedAccountForest<'_, '_>,
986 new_header: &AccountHeader,
987 patch: &AccountPatch,
988 ) -> Result<(), StoreError> {
989 let account_id = new_header.id();
990
991 let init_header = Self::require_latest_account_header(tx, account_id)?;
993
994 if new_header.nonce().as_canonical_u64() <= init_header.nonce().as_canonical_u64() {
995 return Err(StoreError::DatabaseError(format!(
996 "apply_sync_account_patch: new nonce {} is not greater than local nonce {} for account {}",
997 new_header.nonce().as_canonical_u64(),
998 init_header.nonce().as_canonical_u64(),
999 account_id,
1000 )));
1001 }
1002
1003 Self::apply_account_patch(tx, smt_forest, &init_header, new_header, patch)
1006 }
1007
1008 pub(crate) fn lock_account_on_unexpected_commitment(
1011 tx: &Transaction<'_>,
1012 account_id: &AccountId,
1013 mismatched_digest: &Word,
1014 ) -> Result<(), StoreError> {
1015 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)";
1019 let account_id_bytes = account_id.to_bytes();
1020 let digest_bytes = mismatched_digest.to_bytes();
1021 let params = named_params! {
1022 ":account_id": account_id_bytes,
1023 ":digest": digest_bytes
1024 };
1025
1026 let query = format!("UPDATE latest_account_headers SET locked = true {LOCK_CONDITION}");
1027 tx.execute(&query, params).into_store_error()?;
1028
1029 let query = format!("UPDATE historical_account_headers SET locked = true {LOCK_CONDITION}");
1031 tx.execute(&query, params).into_store_error()?;
1032
1033 Ok(())
1034 }
1035
1036 fn insert_new_account_header(
1045 tx: &Transaction<'_>,
1046 new_header: &AccountHeader,
1047 account_seed: Option<Word>,
1048 watched: bool,
1049 ) -> Result<(), StoreError> {
1050 let id = new_header.id().to_bytes();
1051 let code_commitment = new_header.code_commitment().to_bytes();
1052 let storage_commitment = new_header.storage_commitment().to_bytes();
1053 let vault_root = new_header.vault_root().to_bytes();
1054 let nonce = u64_to_value(new_header.nonce().as_canonical_u64());
1055 let commitment = new_header.to_commitment().to_bytes();
1056 let account_seed = account_seed.map(|seed| seed.to_bytes());
1057
1058 const LATEST_QUERY: &str = insert_sql!(
1059 latest_account_headers {
1060 id,
1061 code_commitment,
1062 storage_commitment,
1063 vault_root,
1064 nonce,
1065 account_seed,
1066 account_commitment,
1067 locked,
1068 watched
1069 } | REPLACE
1070 );
1071
1072 tx.execute(
1073 LATEST_QUERY,
1074 params![
1075 id,
1076 code_commitment,
1077 storage_commitment,
1078 vault_root,
1079 nonce,
1080 account_seed,
1081 commitment,
1082 false,
1083 watched,
1084 ],
1085 )
1086 .into_store_error()?;
1087
1088 Ok(())
1089 }
1090
1091 fn replace_account_header(
1097 tx: &Transaction<'_>,
1098 new_header: &AccountHeader,
1099 old_header: &AccountHeader,
1100 ) -> Result<(), StoreError> {
1101 if new_header.id() != old_header.id() {
1102 return Err(StoreError::DatabaseError(format!(
1103 "replace_account_header: account id mismatch (new: {}, old: {})",
1104 new_header.id(),
1105 old_header.id(),
1106 )));
1107 }
1108 if new_header.nonce().as_canonical_u64() < old_header.nonce().as_canonical_u64() {
1109 return Err(StoreError::DatabaseError(format!(
1110 "replace_account_header: new nonce {} is less than old nonce {} for account {}",
1111 new_header.nonce().as_canonical_u64(),
1112 old_header.nonce().as_canonical_u64(),
1113 new_header.id(),
1114 )));
1115 }
1116
1117 let id_bytes = new_header.id().to_bytes();
1118
1119 let (old_seed, old_locked, old_watched): (Option<Vec<u8>>, bool, bool) = tx
1123 .query_row(
1124 "SELECT account_seed, locked, watched FROM latest_account_headers WHERE id = ?",
1125 params![&id_bytes],
1126 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
1127 )
1128 .optional()
1129 .into_store_error()?
1130 .unwrap_or((None, false, false));
1131
1132 let old_id = old_header.id().to_bytes();
1134 let old_code_commitment = old_header.code_commitment().to_bytes();
1135 let old_storage_commitment = old_header.storage_commitment().to_bytes();
1136 let old_vault_root = old_header.vault_root().to_bytes();
1137 let old_nonce = u64_to_value(old_header.nonce().as_canonical_u64());
1138 let old_commitment = old_header.to_commitment().to_bytes();
1139 let replaced_at_nonce = u64_to_value(new_header.nonce().as_canonical_u64());
1140
1141 const HISTORICAL_QUERY: &str = insert_sql!(
1142 historical_account_headers {
1143 id,
1144 code_commitment,
1145 storage_commitment,
1146 vault_root,
1147 nonce,
1148 account_seed,
1149 account_commitment,
1150 locked,
1151 replaced_at_nonce
1152 } | REPLACE
1153 );
1154
1155 tx.execute(
1156 HISTORICAL_QUERY,
1157 params![
1158 old_id,
1159 old_code_commitment,
1160 old_storage_commitment,
1161 old_vault_root,
1162 old_nonce,
1163 old_seed,
1164 old_commitment,
1165 old_locked,
1166 replaced_at_nonce,
1167 ],
1168 )
1169 .into_store_error()?;
1170
1171 Self::insert_new_account_header(tx, new_header, None, old_watched)
1173 }
1174
1175 pub fn prune_account_history(
1181 conn: &mut Connection,
1182 account_id: AccountId,
1183 up_to_nonce: Felt,
1184 ) -> Result<usize, StoreError> {
1185 let tx = conn.transaction().into_store_error()?;
1186 let account_id_bytes = account_id.to_bytes();
1187 let boundary_val = u64_to_value(up_to_nonce.as_canonical_u64());
1188 let mut total_deleted: usize = 0;
1189
1190 let candidate_code_commitments: Vec<Vec<u8>> = {
1192 let mut stmt = tx
1193 .prepare(
1194 "SELECT DISTINCT code_commitment FROM historical_account_headers \
1195 WHERE id = ? AND replaced_at_nonce <= ?",
1196 )
1197 .into_store_error()?;
1198 let rows = stmt
1199 .query_map(params![&account_id_bytes, &boundary_val], |row| row.get(0))
1200 .into_store_error()?;
1201 rows.collect::<Result<Vec<Vec<u8>>, _>>().into_store_error()?
1202 };
1203
1204 total_deleted += tx
1206 .execute(
1207 "DELETE FROM historical_account_headers \
1208 WHERE id = ? AND replaced_at_nonce <= ?",
1209 params![&account_id_bytes, &boundary_val],
1210 )
1211 .into_store_error()?;
1212
1213 total_deleted += tx
1214 .execute(
1215 "DELETE FROM historical_account_storage \
1216 WHERE account_id = ? AND replaced_at_nonce <= ?",
1217 params![&account_id_bytes, &boundary_val],
1218 )
1219 .into_store_error()?;
1220
1221 total_deleted += tx
1222 .execute(
1223 "DELETE FROM historical_storage_map_entries \
1224 WHERE account_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_assets \
1232 WHERE account_id = ? AND replaced_at_nonce <= ?",
1233 params![&account_id_bytes, &boundary_val],
1234 )
1235 .into_store_error()?;
1236
1237 for commitment in &candidate_code_commitments {
1240 let still_referenced: bool = tx
1241 .query_row(
1242 "SELECT EXISTS(
1243 SELECT 1 FROM latest_account_headers WHERE code_commitment = ?1
1244 UNION ALL
1245 SELECT 1 FROM historical_account_headers WHERE code_commitment = ?1
1246 UNION ALL
1247 SELECT 1 FROM foreign_account_code WHERE code_commitment = ?1
1248 )",
1249 params![commitment],
1250 |row| row.get(0),
1251 )
1252 .into_store_error()?;
1253
1254 if !still_referenced {
1255 total_deleted += tx
1256 .execute("DELETE FROM account_code WHERE commitment = ?", params![commitment])
1257 .into_store_error()?;
1258 }
1259 }
1260
1261 tx.commit().into_store_error()?;
1262 Ok(total_deleted)
1263 }
1264}