1use std::collections::BTreeMap;
4use std::rc::Rc;
5use std::string::{String, ToString};
6use std::sync::{Arc, RwLock};
7use std::vec::Vec;
8
9use miden_client::account::{
10 Account,
11 AccountCode,
12 AccountDelta,
13 AccountHeader,
14 AccountId,
15 AccountIdPrefix,
16 AccountStorage,
17 Address,
18 PartialAccount,
19 PartialStorage,
20 PartialStorageMap,
21 StorageMap,
22 StorageMapKey,
23 StorageSlotName,
24 StorageSlotType,
25};
26use miden_client::asset::{Asset, AssetVault, AssetWitness, FungibleAsset};
27use miden_client::store::{
28 AccountRecord,
29 AccountRecordData,
30 AccountSmtForest,
31 AccountStatus,
32 AccountStorageFilter,
33 StoreError,
34};
35use miden_client::sync::NoteTagRecord;
36use miden_client::utils::Serializable;
37use miden_client::{AccountError, Word};
38use miden_protocol::account::{AccountStorageHeader, StorageMapWitness, StorageSlotHeader};
39use miden_protocol::asset::{AssetVaultKey, PartialVault};
40use miden_protocol::crypto::merkle::MerkleError;
41use rusqlite::types::Value;
42use rusqlite::{Connection, Transaction, named_params, params};
43
44use crate::account::helpers::{
45 query_account_addresses,
46 query_account_code,
47 query_historical_account_headers,
48 query_latest_account_headers,
49 query_storage_slots,
50 query_storage_values,
51 query_vault_assets,
52};
53use crate::sql_error::SqlResultExt;
54use crate::sync::{add_note_tag_tx, remove_note_tag_tx};
55use crate::{SqliteStore, column_value_as_u64, insert_sql, subst, u64_to_value};
56
57impl SqliteStore {
58 pub(crate) fn get_account_ids(conn: &mut Connection) -> Result<Vec<AccountId>, StoreError> {
62 const QUERY: &str = "SELECT id FROM latest_account_headers";
63
64 conn.prepare_cached(QUERY)
65 .into_store_error()?
66 .query_map([], |row| row.get(0))
67 .expect("no binding parameters used in query")
68 .map(|result| {
69 let id: String = result.map_err(|e| StoreError::ParsingError(e.to_string()))?;
70 Ok(AccountId::from_hex(&id).expect("account id is valid"))
71 })
72 .collect::<Result<Vec<AccountId>, StoreError>>()
73 }
74
75 pub(crate) fn get_account_headers(
76 conn: &mut Connection,
77 ) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
78 query_latest_account_headers(conn, "1=1 ORDER BY id", params![])
79 }
80
81 pub(crate) fn get_account_header(
82 conn: &mut Connection,
83 account_id: AccountId,
84 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
85 Ok(query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?.pop())
86 }
87
88 pub(crate) fn get_account_header_by_commitment(
89 conn: &mut Connection,
90 account_commitment: Word,
91 ) -> Result<Option<AccountHeader>, StoreError> {
92 let account_commitment_str: String = account_commitment.to_string();
93 Ok(query_historical_account_headers(
94 conn,
95 "account_commitment = ?",
96 params![account_commitment_str],
97 )?
98 .pop()
99 .map(|(header, _)| header))
100 }
101
102 pub(crate) fn get_account(
104 conn: &mut Connection,
105 account_id: AccountId,
106 ) -> Result<Option<AccountRecord>, StoreError> {
107 let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
108 return Ok(None);
109 };
110
111 let assets = query_vault_assets(conn, account_id)?;
112 let vault = AssetVault::new(&assets)?;
113
114 let slots = query_storage_slots(conn, account_id, &AccountStorageFilter::All)?
115 .into_values()
116 .collect();
117
118 let storage = AccountStorage::new(slots)?;
119
120 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
121 return Ok(None);
122 };
123
124 let account = Account::new_unchecked(
125 header.id(),
126 vault,
127 storage,
128 account_code,
129 header.nonce(),
130 status.seed().copied(),
131 );
132
133 let account_data = AccountRecordData::Full(account);
134 Ok(Some(AccountRecord::new(account_data, status)))
135 }
136
137 pub(crate) fn get_minimal_partial_account(
139 conn: &mut Connection,
140 account_id: AccountId,
141 ) -> Result<Option<AccountRecord>, StoreError> {
142 let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
143 return Ok(None);
144 };
145
146 let partial_vault = PartialVault::new(header.vault_root());
148
149 let mut storage_header = Vec::new();
151 let mut maps = vec![];
152
153 let storage_values = query_storage_values(conn, account_id)?;
154
155 for (slot_name, (slot_type, value)) in storage_values {
159 storage_header.push(StorageSlotHeader::new(slot_name.clone(), slot_type, value));
160 if slot_type == StorageSlotType::Map {
161 maps.push(PartialStorageMap::new(value));
162 }
163 }
164 storage_header.sort_by_key(StorageSlotHeader::id);
165 let storage_header =
166 AccountStorageHeader::new(storage_header).map_err(StoreError::AccountError)?;
167 let partial_storage =
168 PartialStorage::new(storage_header, maps).map_err(StoreError::AccountError)?;
169
170 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
171 return Ok(None);
172 };
173
174 let partial_account = PartialAccount::new(
175 header.id(),
176 header.nonce(),
177 account_code,
178 partial_storage,
179 partial_vault,
180 status.seed().copied(),
181 )?;
182 let account_record_data = AccountRecordData::Partial(partial_account);
183 Ok(Some(AccountRecord::new(account_record_data, status)))
184 }
185
186 pub fn get_foreign_account_code(
187 conn: &mut Connection,
188 account_ids: Vec<AccountId>,
189 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
190 let params: Vec<Value> =
191 account_ids.into_iter().map(|id| Value::from(id.to_hex())).collect();
192 const QUERY: &str = "
193 SELECT account_id, code
194 FROM foreign_account_code JOIN account_code ON foreign_account_code.code_commitment = account_code.commitment
195 WHERE account_id IN rarray(?)";
196
197 conn.prepare_cached(QUERY)
198 .into_store_error()?
199 .query_map([Rc::new(params)], |row| Ok((row.get(0)?, row.get(1)?)))
200 .expect("no binding parameters used in query")
201 .map(|result| {
202 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
203 |(id, code): (String, Vec<u8>)| {
204 Ok((
205 AccountId::from_hex(&id).map_err(|err| {
206 StoreError::AccountError(
207 AccountError::FinalAccountHeaderIdParsingFailed(err),
208 )
209 })?,
210 AccountCode::from_bytes(&code).map_err(StoreError::AccountError)?,
211 ))
212 },
213 )
214 })
215 .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
216 }
217
218 pub fn get_account_vault(
220 conn: &Connection,
221 account_id: AccountId,
222 ) -> Result<AssetVault, StoreError> {
223 let assets = query_vault_assets(conn, account_id)?;
224 Ok(AssetVault::new(&assets)?)
225 }
226
227 pub fn get_account_storage(
229 conn: &Connection,
230 account_id: AccountId,
231 filter: &AccountStorageFilter,
232 ) -> Result<AccountStorage, StoreError> {
233 let slots = query_storage_slots(conn, account_id, filter)?.into_values().collect();
234 Ok(AccountStorage::new(slots)?)
235 }
236
237 pub(crate) fn get_account_asset(
240 conn: &mut Connection,
241 smt_forest: &Arc<RwLock<AccountSmtForest>>,
242 account_id: AccountId,
243 vault_key: AssetVaultKey,
244 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
245 let header = Self::get_account_header(conn, account_id)?
246 .ok_or(StoreError::AccountDataNotFound(account_id))?
247 .0;
248
249 let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
250 match smt_forest.get_asset_and_witness(header.vault_root(), vault_key) {
251 Ok((asset, witness)) => Ok(Some((asset, witness))),
252 Err(StoreError::MerkleStoreError(MerkleError::UntrackedKey(_))) => Ok(None),
253 Err(err) => Err(err),
254 }
255 }
256
257 pub(crate) fn get_account_map_item(
260 conn: &mut Connection,
261 smt_forest: &Arc<RwLock<AccountSmtForest>>,
262 account_id: AccountId,
263 slot_name: StorageSlotName,
264 key: StorageMapKey,
265 ) -> Result<(Word, StorageMapWitness), StoreError> {
266 let header = Self::get_account_header(conn, account_id)?
267 .ok_or(StoreError::AccountDataNotFound(account_id))?
268 .0;
269
270 let mut storage_values = query_storage_values(conn, 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 = smt_forest.read().expect("smt_forest read lock not poisoned");
279 let witness = smt_forest.get_storage_map_item_witness(map_root, key)?;
280 let item = witness.get(key).unwrap_or(miden_client::EMPTY_WORD);
281
282 Ok((item, witness))
283 }
284
285 pub(crate) fn get_account_addresses(
286 conn: &mut Connection,
287 account_id: AccountId,
288 ) -> Result<Vec<Address>, StoreError> {
289 query_account_addresses(conn, account_id)
290 }
291
292 pub(crate) fn get_account_code_by_id(
294 conn: &mut Connection,
295 account_id: AccountId,
296 ) -> Result<Option<AccountCode>, StoreError> {
297 let Some((header, _)) =
298 query_latest_account_headers(conn, "id = ?", params![account_id.to_hex()])?
299 .into_iter()
300 .next()
301 else {
302 return Ok(None);
303 };
304
305 query_account_code(conn, header.code_commitment())
306 }
307
308 pub(crate) fn insert_account(
312 conn: &mut Connection,
313 smt_forest: &Arc<RwLock<AccountSmtForest>>,
314 account: &Account,
315 initial_address: &Address,
316 ) -> Result<(), StoreError> {
317 let tx = conn.transaction().into_store_error()?;
318
319 Self::insert_account_code(&tx, account.code())?;
320
321 let account_id = account.id();
322 let nonce = account.nonce().as_int();
323
324 Self::insert_storage_slots(&tx, account_id, nonce, account.storage().slots().iter())?;
325
326 Self::insert_assets(&tx, account_id, nonce, account.vault().assets())?;
327 Self::insert_account_header(&tx, &account.into(), account.seed())?;
328
329 Self::insert_address(&tx, initial_address, account.id())?;
330
331 tx.commit().into_store_error()?;
332
333 let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
334 smt_forest.insert_and_register_account_state(
335 account.id(),
336 account.vault(),
337 account.storage(),
338 )?;
339
340 Ok(())
341 }
342
343 pub(crate) fn update_account(
344 conn: &mut Connection,
345 smt_forest: &Arc<RwLock<AccountSmtForest>>,
346 new_account_state: &Account,
347 ) -> Result<(), StoreError> {
348 const QUERY: &str = "SELECT id FROM latest_account_headers WHERE id = ?";
349 if conn
350 .prepare(QUERY)
351 .into_store_error()?
352 .query_map(params![new_account_state.id().to_hex()], |row| row.get(0))
353 .into_store_error()?
354 .map(|result| {
355 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
356 |id: String| {
357 AccountId::from_hex(&id).map_err(|err| {
358 StoreError::AccountError(
359 AccountError::FinalAccountHeaderIdParsingFailed(err),
360 )
361 })
362 },
363 )
364 })
365 .next()
366 .is_none()
367 {
368 return Err(StoreError::AccountDataNotFound(new_account_state.id()));
369 }
370
371 let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
372 let tx = conn.transaction().into_store_error()?;
373 Self::update_account_state(&tx, &mut smt_forest, new_account_state)?;
374 tx.commit().into_store_error()
375 }
376
377 pub fn upsert_foreign_account_code(
378 conn: &mut Connection,
379 account_id: AccountId,
380 code: &AccountCode,
381 ) -> Result<(), StoreError> {
382 let tx = conn.transaction().into_store_error()?;
383
384 Self::insert_account_code(&tx, code)?;
385
386 const QUERY: &str =
387 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
388
389 tx.execute(QUERY, params![account_id.to_hex(), code.commitment().to_string()])
390 .into_store_error()?;
391
392 Self::insert_account_code(&tx, code)?;
393 tx.commit().into_store_error()
394 }
395
396 pub(crate) fn insert_address(
397 tx: &Transaction<'_>,
398 address: &Address,
399 account_id: AccountId,
400 ) -> Result<(), StoreError> {
401 let derived_note_tag = address.to_note_tag();
402 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
403
404 add_note_tag_tx(tx, ¬e_tag_record)?;
405 Self::insert_address_internal(tx, address, account_id)?;
406
407 Ok(())
408 }
409
410 pub(crate) fn remove_address(
411 conn: &mut Connection,
412 address: &Address,
413 account_id: AccountId,
414 ) -> Result<(), StoreError> {
415 let derived_note_tag = address.to_note_tag();
416 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
417
418 let tx = conn.transaction().into_store_error()?;
419 remove_note_tag_tx(&tx, note_tag_record)?;
420 Self::remove_address_internal(&tx, address)?;
421
422 tx.commit().into_store_error()
423 }
424
425 pub(crate) fn insert_account_code(
427 tx: &Transaction<'_>,
428 account_code: &AccountCode,
429 ) -> Result<(), StoreError> {
430 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
431 tx.execute(QUERY, params![account_code.commitment().to_hex(), account_code.to_bytes()])
432 .into_store_error()?;
433 Ok(())
434 }
435
436 pub(crate) fn apply_account_delta(
441 tx: &Transaction<'_>,
442 smt_forest: &mut AccountSmtForest,
443 init_account_state: &AccountHeader,
444 final_account_state: &AccountHeader,
445 updated_fungible_assets: BTreeMap<AccountIdPrefix, FungibleAsset>,
446 old_map_roots: &BTreeMap<StorageSlotName, Word>,
447 delta: &AccountDelta,
448 ) -> Result<(), StoreError> {
449 let account_id = final_account_state.id();
450
451 Self::insert_account_header(tx, final_account_state, None)?;
453
454 Self::apply_account_vault_delta(
455 tx,
456 smt_forest,
457 account_id,
458 init_account_state,
459 final_account_state,
460 updated_fungible_assets,
461 delta,
462 )?;
463
464 let mut final_roots = smt_forest
469 .get_roots(&init_account_state.id())
470 .cloned()
471 .ok_or(StoreError::AccountDataNotFound(init_account_state.id()))?;
472
473 if let Some(vault_root) = final_roots.first_mut() {
475 *vault_root = final_account_state.vault_root();
476 }
477
478 let default_map_root = StorageMap::default().root();
479 let updated_storage_slots =
480 Self::apply_account_storage_delta(smt_forest, old_map_roots, delta)?;
481
482 for (slot_name, (new_root, slot_type)) in &updated_storage_slots {
484 if *slot_type == StorageSlotType::Map {
485 let old_root = old_map_roots.get(slot_name).copied().unwrap_or(default_map_root);
486 if let Some(root) = final_roots.iter_mut().find(|r| **r == old_root) {
487 *root = *new_root;
488 } else {
489 final_roots.push(*new_root);
491 }
492 }
493 }
494
495 Self::write_storage_delta(
496 tx,
497 account_id,
498 final_account_state.nonce().as_int(),
499 &updated_storage_slots,
500 delta,
501 )?;
502
503 smt_forest.stage_roots(final_account_state.id(), final_roots);
504
505 Ok(())
506 }
507
508 pub(crate) fn undo_account_state(
511 tx: &Transaction<'_>,
512 smt_forest: &mut AccountSmtForest,
513 discarded_states: &[(AccountId, Word)],
514 ) -> Result<(), StoreError> {
515 if discarded_states.is_empty() {
516 return Ok(());
517 }
518
519 let commitment_params = Rc::new(
520 discarded_states
521 .iter()
522 .map(|(_, commitment)| Value::from(commitment.to_hex()))
523 .collect::<Vec<_>>(),
524 );
525
526 const RESOLVE_QUERY: &str = "SELECT id, nonce FROM historical_account_headers WHERE account_commitment IN rarray(?)";
528 let id_nonce_pairs: Vec<(String, u64)> = tx
529 .prepare(RESOLVE_QUERY)
530 .into_store_error()?
531 .query_map(params![commitment_params.clone()], |row| {
532 let id: String = row.get(0)?;
533 let nonce: u64 = column_value_as_u64(row, 1)?;
534 Ok((id, nonce))
535 })
536 .into_store_error()?
537 .filter_map(Result::ok)
538 .collect();
539
540 for (account_id_hex, nonce) in &id_nonce_pairs {
542 let nonce_val = u64_to_value(*nonce);
543 tx.execute(
544 "DELETE FROM historical_account_storage WHERE account_id = ? AND nonce = ?",
545 params![account_id_hex, nonce_val],
546 )
547 .into_store_error()?;
548 tx.execute(
549 "DELETE FROM historical_storage_map_entries WHERE account_id = ? AND nonce = ?",
550 params![account_id_hex, nonce_val],
551 )
552 .into_store_error()?;
553 tx.execute(
554 "DELETE FROM historical_account_assets WHERE account_id = ? AND nonce = ?",
555 params![account_id_hex, nonce_val],
556 )
557 .into_store_error()?;
558 }
559
560 const DELETE_QUERY: &str =
562 "DELETE FROM historical_account_headers WHERE account_commitment IN rarray(?)";
563 tx.execute(DELETE_QUERY, params![commitment_params]).into_store_error()?;
564
565 let mut unique_ids: Vec<String> = id_nonce_pairs.iter().map(|(id, _)| id.clone()).collect();
567 unique_ids.sort();
568 unique_ids.dedup();
569
570 for account_id_hex in &unique_ids {
571 Self::rebuild_latest_for_account(tx, account_id_hex)?;
572 }
573
574 for (account_id, _) in discarded_states {
577 smt_forest.discard_roots(*account_id);
578 }
579
580 Ok(())
581 }
582
583 pub(crate) fn update_account_state(
588 tx: &Transaction<'_>,
589 smt_forest: &mut AccountSmtForest,
590 new_account_state: &Account,
591 ) -> Result<(), StoreError> {
592 let account_id = new_account_state.id();
593 let account_id_hex = account_id.to_hex();
594 let nonce = new_account_state.nonce().as_int();
595 let nonce_val = u64_to_value(nonce);
596
597 smt_forest.insert_and_register_account_state(
599 account_id,
600 new_account_state.vault(),
601 new_account_state.storage(),
602 )?;
603
604 tx.execute(
608 "INSERT OR REPLACE INTO historical_storage_map_entries \
609 (account_id, nonce, slot_name, key, value) \
610 SELECT account_id, ?, slot_name, key, NULL \
611 FROM latest_storage_map_entries WHERE account_id = ?",
612 params![&nonce_val, &account_id_hex],
613 )
614 .into_store_error()?;
615 tx.execute(
616 "INSERT OR REPLACE INTO historical_account_assets \
617 (account_id, nonce, vault_key, faucet_id_prefix, asset) \
618 SELECT account_id, ?, vault_key, faucet_id_prefix, NULL \
619 FROM latest_account_assets WHERE account_id = ?",
620 params![&nonce_val, &account_id_hex],
621 )
622 .into_store_error()?;
623
624 tx.execute(
626 "DELETE FROM latest_account_storage WHERE account_id = ?",
627 params![&account_id_hex],
628 )
629 .into_store_error()?;
630 tx.execute(
631 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
632 params![&account_id_hex],
633 )
634 .into_store_error()?;
635 tx.execute(
636 "DELETE FROM latest_account_assets WHERE account_id = ?",
637 params![&account_id_hex],
638 )
639 .into_store_error()?;
640
641 Self::insert_storage_slots(
644 tx,
645 account_id,
646 nonce,
647 new_account_state.storage().slots().iter(),
648 )?;
649 Self::insert_assets(tx, account_id, nonce, new_account_state.vault().assets())?;
650 Self::insert_account_header(tx, &new_account_state.into(), None)?;
651
652 Ok(())
653 }
654
655 pub(crate) fn lock_account_on_unexpected_commitment(
658 tx: &Transaction<'_>,
659 account_id: &AccountId,
660 mismatched_digest: &Word,
661 ) -> Result<(), StoreError> {
662 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)";
666 let account_id_hex = account_id.to_hex();
667 let digest_str = mismatched_digest.to_string();
668 let params = named_params! {
669 ":account_id": account_id_hex,
670 ":digest": digest_str
671 };
672
673 let query = format!("UPDATE latest_account_headers SET locked = true {LOCK_CONDITION}");
674 tx.execute(&query, params).into_store_error()?;
675
676 let query = format!("UPDATE historical_account_headers SET locked = true {LOCK_CONDITION}");
678 tx.execute(&query, params).into_store_error()?;
679
680 Ok(())
681 }
682
683 fn rebuild_latest_for_account(
689 tx: &Transaction<'_>,
690 account_id_hex: &str,
691 ) -> Result<(), StoreError> {
692 let remaining: Option<u64> = tx
693 .query_row(
694 "SELECT MAX(nonce) FROM historical_account_headers WHERE id = ?",
695 params![account_id_hex],
696 |row| row.get(0),
697 )
698 .into_store_error()?;
699
700 tx.execute("DELETE FROM latest_account_headers WHERE id = ?", params![account_id_hex])
702 .into_store_error()?;
703 tx.execute(
704 "DELETE FROM latest_account_storage WHERE account_id = ?",
705 params![account_id_hex],
706 )
707 .into_store_error()?;
708 tx.execute(
709 "DELETE FROM latest_storage_map_entries WHERE account_id = ?",
710 params![account_id_hex],
711 )
712 .into_store_error()?;
713 tx.execute(
714 "DELETE FROM latest_account_assets WHERE account_id = ?",
715 params![account_id_hex],
716 )
717 .into_store_error()?;
718
719 if remaining.is_none() {
720 return Ok(());
721 }
722
723 tx.execute(
725 "INSERT INTO latest_account_headers (id, account_commitment, code_commitment, storage_commitment, vault_root, nonce, account_seed, locked)
726 SELECT h.id, h.account_commitment, h.code_commitment, h.storage_commitment, h.vault_root, h.nonce, h.account_seed, h.locked
727 FROM historical_account_headers h
728 WHERE h.id = ?1 AND h.nonce = (SELECT MAX(nonce) FROM historical_account_headers WHERE id = ?1)",
729 params![account_id_hex],
730 )
731 .into_store_error()?;
732
733 tx.execute(
735 "INSERT INTO latest_account_storage (account_id, slot_name, slot_value, slot_type)
736 SELECT h.account_id, h.slot_name, h.slot_value, h.slot_type
737 FROM historical_account_storage h
738 INNER JOIN (
739 SELECT account_id, slot_name, MAX(nonce) AS max_nonce
740 FROM historical_account_storage WHERE account_id = ?1
741 GROUP BY account_id, slot_name
742 ) latest ON h.account_id = latest.account_id
743 AND h.slot_name = latest.slot_name AND h.nonce = latest.max_nonce",
744 params![account_id_hex],
745 )
746 .into_store_error()?;
747
748 tx.execute(
749 "INSERT INTO latest_storage_map_entries (account_id, slot_name, key, value)
750 SELECT h.account_id, h.slot_name, h.key, h.value
751 FROM historical_storage_map_entries h
752 INNER JOIN (
753 SELECT account_id, slot_name, key, MAX(nonce) AS max_nonce
754 FROM historical_storage_map_entries WHERE account_id = ?1
755 GROUP BY account_id, slot_name, key
756 ) latest ON h.account_id = latest.account_id
757 AND h.slot_name = latest.slot_name AND h.key = latest.key
758 AND h.nonce = latest.max_nonce
759 WHERE h.value IS NOT NULL",
760 params![account_id_hex],
761 )
762 .into_store_error()?;
763
764 tx.execute(
765 "INSERT INTO latest_account_assets (account_id, vault_key, faucet_id_prefix, asset)
766 SELECT h.account_id, h.vault_key, h.faucet_id_prefix, h.asset
767 FROM historical_account_assets h
768 INNER JOIN (
769 SELECT account_id, vault_key, MAX(nonce) AS max_nonce
770 FROM historical_account_assets WHERE account_id = ?1
771 GROUP BY account_id, vault_key
772 ) latest ON h.account_id = latest.account_id
773 AND h.vault_key = latest.vault_key AND h.nonce = latest.max_nonce
774 WHERE h.asset IS NOT NULL",
775 params![account_id_hex],
776 )
777 .into_store_error()?;
778
779 Ok(())
780 }
781
782 fn insert_account_header(
787 tx: &Transaction<'_>,
788 account: &AccountHeader,
789 account_seed: Option<Word>,
790 ) -> Result<(), StoreError> {
791 let id: String = account.id().to_hex();
792 let code_commitment = account.code_commitment().to_string();
793 let storage_commitment = account.storage_commitment().to_string();
794 let vault_root = account.vault_root().to_string();
795 let nonce = u64_to_value(account.nonce().as_int());
796 let commitment = account.to_commitment().to_string();
797
798 let account_seed = account_seed.map(|seed| seed.to_bytes());
799
800 const HISTORICAL_QUERY: &str = insert_sql!(
801 historical_account_headers {
802 id,
803 code_commitment,
804 storage_commitment,
805 vault_root,
806 nonce,
807 account_seed,
808 account_commitment,
809 locked
810 } | REPLACE
811 );
812
813 tx.execute(
814 HISTORICAL_QUERY,
815 params![
816 id,
817 code_commitment,
818 storage_commitment,
819 vault_root,
820 nonce,
821 account_seed,
822 commitment,
823 false,
824 ],
825 )
826 .into_store_error()?;
827
828 const LATEST_QUERY: &str = insert_sql!(
829 latest_account_headers {
830 id,
831 code_commitment,
832 storage_commitment,
833 vault_root,
834 nonce,
835 account_seed,
836 account_commitment,
837 locked
838 } | REPLACE
839 );
840
841 tx.execute(
842 LATEST_QUERY,
843 params![
844 id,
845 code_commitment,
846 storage_commitment,
847 vault_root,
848 nonce,
849 account_seed,
850 commitment,
851 false,
852 ],
853 )
854 .into_store_error()?;
855
856 Ok(())
857 }
858
859 fn insert_address_internal(
860 tx: &Transaction<'_>,
861 address: &Address,
862 account_id: AccountId,
863 ) -> Result<(), StoreError> {
864 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
865 let serialized_address = address.to_bytes();
866 tx.execute(QUERY, params![serialized_address, account_id.to_hex(),])
867 .into_store_error()?;
868
869 Ok(())
870 }
871
872 fn remove_address_internal(tx: &Transaction<'_>, address: &Address) -> Result<(), StoreError> {
873 let serialized_address = address.to_bytes();
874
875 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
876 tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
877
878 Ok(())
879 }
880}