Skip to main content

miden_client_sqlite_store/account/
accounts.rs

1//! Account-related database operations.
2
3use 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    StorageSlotName,
23    StorageSlotType,
24};
25use miden_client::asset::{Asset, AssetVault, AssetWitness, FungibleAsset};
26use miden_client::store::{
27    AccountRecord,
28    AccountRecordData,
29    AccountStatus,
30    AccountStorageFilter,
31    StoreError,
32};
33use miden_client::sync::NoteTagRecord;
34use miden_client::utils::Serializable;
35use miden_client::{AccountError, Word};
36use miden_protocol::account::{AccountStorageHeader, StorageMapWitness, StorageSlotHeader};
37use miden_protocol::asset::{AssetVaultKey, PartialVault};
38use miden_protocol::crypto::merkle::MerkleError;
39use rusqlite::types::Value;
40use rusqlite::{Connection, Transaction, named_params, params};
41
42use crate::account::helpers::{
43    SerializedHeaderData,
44    parse_accounts,
45    query_account_addresses,
46    query_account_code,
47    query_account_headers,
48    query_storage_slots,
49    query_storage_values,
50    query_vault_assets,
51};
52use crate::smt_forest::AccountSmtForest;
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    // READER METHODS
59    // --------------------------------------------------------------------------------------------
60
61    pub(crate) fn get_account_ids(conn: &mut Connection) -> Result<Vec<AccountId>, StoreError> {
62        const QUERY: &str = "SELECT id FROM tracked_accounts";
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        const QUERY: &str = "
79            SELECT
80                a.id,
81                a.nonce,
82                a.vault_root,
83                a.storage_commitment,
84                a.code_commitment,
85                a.account_seed,
86                a.locked
87            FROM accounts AS a
88            JOIN (
89                SELECT id, MAX(nonce) AS nonce
90                FROM accounts
91                GROUP BY id
92            ) AS latest
93            ON a.id = latest.id
94            AND a.nonce = latest.nonce
95            ORDER BY a.id;
96            ";
97
98        conn.prepare_cached(QUERY)
99            .into_store_error()?
100            .query_map(params![], |row| {
101                let id: String = row.get(0)?;
102                let nonce: u64 = column_value_as_u64(row, 1)?;
103                let vault_root: String = row.get(2)?;
104                let storage_commitment: String = row.get(3)?;
105                let code_commitment: String = row.get(4)?;
106                let account_seed: Option<Vec<u8>> = row.get(5)?;
107                let locked: bool = row.get(6)?;
108
109                Ok(SerializedHeaderData {
110                    id,
111                    nonce,
112                    vault_root,
113                    storage_commitment,
114                    code_commitment,
115                    account_seed,
116                    locked,
117                })
118            })
119            .into_store_error()?
120            .map(|result| parse_accounts(result.into_store_error()?))
121            .collect::<Result<Vec<(AccountHeader, AccountStatus)>, StoreError>>()
122    }
123
124    pub(crate) fn get_account_header(
125        conn: &mut Connection,
126        account_id: AccountId,
127    ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
128        Ok(query_account_headers(
129            conn,
130            "id = ? ORDER BY nonce DESC LIMIT 1",
131            params![account_id.to_hex()],
132        )?
133        .pop())
134    }
135
136    pub(crate) fn get_account_header_by_commitment(
137        conn: &mut Connection,
138        account_commitment: Word,
139    ) -> Result<Option<AccountHeader>, StoreError> {
140        let account_commitment_str: String = account_commitment.to_string();
141        Ok(
142            query_account_headers(conn, "account_commitment = ?", params![account_commitment_str])?
143                .pop()
144                .map(|(header, _)| header),
145        )
146    }
147
148    /// Retrieves a complete account record with full vault and storage data.
149    pub(crate) fn get_account(
150        conn: &mut Connection,
151        account_id: AccountId,
152    ) -> Result<Option<AccountRecord>, StoreError> {
153        let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
154            return Ok(None);
155        };
156
157        let assets = query_vault_assets(conn, "root = ?", params![header.vault_root().to_hex()])?;
158        let vault = AssetVault::new(&assets)?;
159
160        let slots = query_storage_slots(
161            conn,
162            "commitment = ?",
163            params![header.storage_commitment().to_hex()],
164        )?;
165
166        let storage = AccountStorage::new(slots)?;
167
168        let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
169            return Ok(None);
170        };
171
172        let account = Account::new_unchecked(
173            header.id(),
174            vault,
175            storage,
176            account_code,
177            header.nonce(),
178            status.seed().copied(),
179        );
180
181        let addresses = query_account_addresses(conn, header.id())?;
182        let account_data = AccountRecordData::Full(account);
183        Ok(Some(AccountRecord::new(account_data, status, addresses)))
184    }
185
186    /// Retrieves a minimal partial account record with storage and vault witnesses.
187    pub(crate) fn get_minimal_partial_account(
188        conn: &mut Connection,
189        account_id: AccountId,
190    ) -> Result<Option<AccountRecord>, StoreError> {
191        let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
192            return Ok(None);
193        };
194
195        // Partial vault retrieval
196        let partial_vault = PartialVault::new(header.vault_root());
197
198        // Partial storage retrieval
199        let mut storage_header = Vec::new();
200        let mut maps = vec![];
201
202        let storage_values = query_storage_values(
203            conn,
204            "commitment = ?",
205            params![header.storage_commitment().to_hex()],
206        )?;
207
208        // Storage maps are always minimal here (just roots, no entries).
209        // New accounts that need full storage data are handled by the DataStore layer,
210        // which fetches the full account via `get_account()` when nonce == 0.
211        for (slot_name, (slot_type, value)) in storage_values {
212            storage_header.push(StorageSlotHeader::new(slot_name.clone(), slot_type, value));
213            if slot_type == StorageSlotType::Map {
214                maps.push(PartialStorageMap::new(value));
215            }
216        }
217        storage_header.sort_by_key(StorageSlotHeader::id);
218        let storage_header =
219            AccountStorageHeader::new(storage_header).map_err(StoreError::AccountError)?;
220        let partial_storage =
221            PartialStorage::new(storage_header, maps).map_err(StoreError::AccountError)?;
222
223        let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
224            return Ok(None);
225        };
226
227        let partial_account = PartialAccount::new(
228            header.id(),
229            header.nonce(),
230            account_code,
231            partial_storage,
232            partial_vault,
233            status.seed().copied(),
234        )?;
235        let account_record_data = AccountRecordData::Partial(partial_account);
236        let addresses = query_account_addresses(conn, header.id())?;
237        Ok(Some(AccountRecord::new(account_record_data, status, addresses)))
238    }
239
240    pub fn get_foreign_account_code(
241        conn: &mut Connection,
242        account_ids: Vec<AccountId>,
243    ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
244        let params: Vec<Value> =
245            account_ids.into_iter().map(|id| Value::from(id.to_hex())).collect();
246        const QUERY: &str = "
247            SELECT account_id, code
248            FROM foreign_account_code JOIN account_code ON foreign_account_code.code_commitment = account_code.commitment
249            WHERE account_id IN rarray(?)";
250
251        conn.prepare_cached(QUERY)
252            .into_store_error()?
253            .query_map([Rc::new(params)], |row| Ok((row.get(0)?, row.get(1)?)))
254            .expect("no binding parameters used in query")
255            .map(|result| {
256                result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
257                    |(id, code): (String, Vec<u8>)| {
258                        Ok((
259                            AccountId::from_hex(&id).map_err(|err| {
260                                StoreError::AccountError(
261                                    AccountError::FinalAccountHeaderIdParsingFailed(err),
262                                )
263                            })?,
264                            AccountCode::from_bytes(&code).map_err(StoreError::AccountError)?,
265                        ))
266                    },
267                )
268            })
269            .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
270    }
271
272    /// Retrieves the full asset vault for a specific account.
273    pub fn get_account_vault(
274        conn: &Connection,
275        account_id: AccountId,
276    ) -> Result<AssetVault, StoreError> {
277        let assets = query_vault_assets(
278            conn,
279            "root = (SELECT vault_root FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1)",
280            params![account_id.to_hex()],
281        )?;
282
283        Ok(AssetVault::new(&assets)?)
284    }
285
286    /// Retrieves the full storage for a specific account.
287    pub fn get_account_storage(
288        conn: &Connection,
289        account_id: AccountId,
290        filter: &AccountStorageFilter,
291    ) -> Result<AccountStorage, StoreError> {
292        let (where_clause, params) = match filter {
293            AccountStorageFilter::All => (
294                "commitment = (SELECT storage_commitment FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1)",
295                params![account_id.to_hex()],
296            ),
297            AccountStorageFilter::Root(root) => (
298                "commitment = (SELECT storage_commitment FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1) AND slot_value = ?",
299                params![account_id.to_hex(), root.to_hex()],
300            ),
301            AccountStorageFilter::SlotName(slot_name) => (
302                "commitment = (SELECT storage_commitment FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1) AND slot_name = ?",
303                params![account_id.to_hex(), slot_name.to_string()],
304            ),
305        };
306
307        let slots = query_storage_slots(conn, where_clause, params)?;
308
309        Ok(AccountStorage::new(slots)?)
310    }
311
312    /// Fetches a specific asset from the account's vault without the need of loading the entire
313    /// vault. The witness is retrieved from the [`AccountSmtForest`].
314    pub(crate) fn get_account_asset(
315        conn: &mut Connection,
316        smt_forest: &Arc<RwLock<AccountSmtForest>>,
317        account_id: AccountId,
318        vault_key: AssetVaultKey,
319    ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
320        let header = Self::get_account_header(conn, account_id)?
321            .ok_or(StoreError::AccountDataNotFound(account_id))?
322            .0;
323
324        let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
325        match smt_forest.get_asset_and_witness(header.vault_root(), vault_key) {
326            Ok((asset, witness)) => Ok(Some((asset, witness))),
327            Err(StoreError::MerkleStoreError(MerkleError::UntrackedKey(_))) => Ok(None),
328            Err(err) => Err(err),
329        }
330    }
331
332    /// Retrieves a specific item from the account's storage map without loading the entire storage.
333    /// The witness is retrieved from the [`AccountSmtForest`].
334    pub(crate) fn get_account_map_item(
335        conn: &mut Connection,
336        smt_forest: &Arc<RwLock<AccountSmtForest>>,
337        account_id: AccountId,
338        slot_name: StorageSlotName,
339        key: Word,
340    ) -> Result<(Word, StorageMapWitness), StoreError> {
341        let header = Self::get_account_header(conn, account_id)?
342            .ok_or(StoreError::AccountDataNotFound(account_id))?
343            .0;
344
345        let mut storage_values = query_storage_values(
346            conn,
347            "commitment = ? AND slot_name = ?",
348            params![header.storage_commitment().to_hex(), slot_name.to_string()],
349        )?;
350        let (slot_type, map_root) = storage_values
351            .remove(&slot_name)
352            .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))?;
353        if slot_type != StorageSlotType::Map {
354            return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name)));
355        }
356
357        let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
358        let witness = smt_forest.get_storage_map_item_witness(map_root, key)?;
359        let item = witness.get(&key).unwrap_or(miden_client::EMPTY_WORD);
360
361        Ok((item, witness))
362    }
363
364    pub(crate) fn get_account_addresses(
365        conn: &mut Connection,
366        account_id: AccountId,
367    ) -> Result<Vec<Address>, StoreError> {
368        query_account_addresses(conn, account_id)
369    }
370
371    // MUTATOR/WRITER METHODS
372    // --------------------------------------------------------------------------------------------
373
374    pub(crate) fn insert_account(
375        conn: &mut Connection,
376        smt_forest: &Arc<RwLock<AccountSmtForest>>,
377        account: &Account,
378        initial_address: &Address,
379    ) -> Result<(), StoreError> {
380        let tx = conn.transaction().into_store_error()?;
381
382        Self::insert_account_code(&tx, account.code())?;
383
384        Self::insert_storage_slots(
385            &tx,
386            account.storage().to_commitment(),
387            account.storage().slots().iter(),
388        )?;
389
390        Self::insert_assets(&tx, account.vault().root(), account.vault().assets())?;
391        Self::insert_account_header(&tx, &account.into(), account.seed())?;
392
393        Self::insert_address(&tx, initial_address, account.id())?;
394
395        tx.commit().into_store_error()?;
396
397        let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
398        smt_forest.insert_and_register_account_state(
399            account.id(),
400            account.vault(),
401            account.storage(),
402        )?;
403
404        Ok(())
405    }
406
407    pub(crate) fn update_account(
408        conn: &mut Connection,
409        smt_forest: &Arc<RwLock<AccountSmtForest>>,
410        new_account_state: &Account,
411    ) -> Result<(), StoreError> {
412        const QUERY: &str = "SELECT id FROM accounts WHERE id = ?";
413        if conn
414            .prepare(QUERY)
415            .into_store_error()?
416            .query_map(params![new_account_state.id().to_hex()], |row| row.get(0))
417            .into_store_error()?
418            .map(|result| {
419                result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
420                    |id: String| {
421                        AccountId::from_hex(&id).map_err(|err| {
422                            StoreError::AccountError(
423                                AccountError::FinalAccountHeaderIdParsingFailed(err),
424                            )
425                        })
426                    },
427                )
428            })
429            .next()
430            .is_none()
431        {
432            return Err(StoreError::AccountDataNotFound(new_account_state.id()));
433        }
434
435        let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
436        let tx = conn.transaction().into_store_error()?;
437        Self::update_account_state(&tx, &mut smt_forest, new_account_state)?;
438        tx.commit().into_store_error()
439    }
440
441    pub fn upsert_foreign_account_code(
442        conn: &mut Connection,
443        account_id: AccountId,
444        code: &AccountCode,
445    ) -> Result<(), StoreError> {
446        let tx = conn.transaction().into_store_error()?;
447
448        Self::insert_account_code(&tx, code)?;
449
450        const QUERY: &str =
451            insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
452
453        tx.execute(QUERY, params![account_id.to_hex(), code.commitment().to_string()])
454            .into_store_error()?;
455
456        Self::insert_account_code(&tx, code)?;
457        tx.commit().into_store_error()
458    }
459
460    pub(crate) fn insert_address(
461        tx: &Transaction<'_>,
462        address: &Address,
463        account_id: AccountId,
464    ) -> Result<(), StoreError> {
465        let derived_note_tag = address.to_note_tag();
466        let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
467
468        add_note_tag_tx(tx, &note_tag_record)?;
469        Self::insert_address_internal(tx, address, account_id)?;
470
471        Ok(())
472    }
473
474    pub(crate) fn remove_address(
475        conn: &mut Connection,
476        address: &Address,
477        account_id: AccountId,
478    ) -> Result<(), StoreError> {
479        let derived_note_tag = address.to_note_tag();
480        let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
481
482        let tx = conn.transaction().into_store_error()?;
483        remove_note_tag_tx(&tx, note_tag_record)?;
484        Self::remove_address_internal(&tx, address)?;
485
486        tx.commit().into_store_error()
487    }
488
489    /// Inserts an [`AccountCode`].
490    pub(crate) fn insert_account_code(
491        tx: &Transaction<'_>,
492        account_code: &AccountCode,
493    ) -> Result<(), StoreError> {
494        const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
495        tx.execute(QUERY, params![account_code.commitment().to_hex(), account_code.to_bytes()])
496            .into_store_error()?;
497        Ok(())
498    }
499
500    /// Applies the account delta to the account state, updating the vault and storage maps.
501    ///
502    /// The apply delta operation strats by copying over the initial account state (vault and
503    /// storage) and then applying the delta on top of it. The storage and vault elements are
504    /// overwritten in the new state. In the cases where the delta depends on previous state (e.g.
505    /// adding or subtracting fungible assets), the previous state needs to be provided via the
506    /// `updated_fungible_assets` and `updated_storage_maps` parameters.
507    pub(crate) fn apply_account_delta(
508        tx: &Transaction<'_>,
509        smt_forest: &mut AccountSmtForest,
510        init_account_state: &AccountHeader,
511        final_account_state: &AccountHeader,
512        updated_fungible_assets: BTreeMap<AccountIdPrefix, FungibleAsset>,
513        updated_storage_maps: BTreeMap<StorageSlotName, StorageMap>,
514        delta: &AccountDelta,
515    ) -> Result<(), StoreError> {
516        // Copy over the storage and vault from the previous state. Non-relevant data will not be
517        // modified.
518        Self::copy_account_state(tx, init_account_state, final_account_state)?;
519
520        Self::apply_account_vault_delta(
521            tx,
522            smt_forest,
523            init_account_state,
524            final_account_state,
525            updated_fungible_assets,
526            delta,
527        )?;
528
529        // Build the final roots from the init state's registered roots:
530        // - Replace vault root with the final one
531        // - Replace changed map roots with their new values (done by apply_account_storage_delta)
532        // - Unchanged map roots continue as they were
533        let mut final_roots = smt_forest
534            .get_roots(&init_account_state.id())
535            .cloned()
536            .ok_or(StoreError::AccountDataNotFound(init_account_state.id()))?;
537
538        // First element is always the vault root
539        if let Some(vault_root) = final_roots.first_mut() {
540            *vault_root = final_account_state.vault_root();
541        }
542
543        let updated_storage_slots = Self::apply_account_storage_delta(
544            smt_forest,
545            &mut final_roots,
546            updated_storage_maps,
547            delta,
548        )?;
549
550        Self::insert_storage_slots(
551            tx,
552            final_account_state.storage_commitment(),
553            updated_storage_slots.iter(),
554        )?;
555
556        smt_forest.stage_roots(final_account_state.id(), final_roots);
557
558        Ok(())
559    }
560
561    /// Removes discarded account states from the database and restores previous
562    /// roots in the SMT forest.
563    pub(crate) fn undo_account_state(
564        tx: &Transaction<'_>,
565        smt_forest: &mut AccountSmtForest,
566        discarded_states: &[(AccountId, Word)],
567    ) -> Result<(), StoreError> {
568        if discarded_states.is_empty() {
569            return Ok(());
570        }
571
572        let commitment_params = Rc::new(
573            discarded_states
574                .iter()
575                .map(|(_, commitment)| Value::from(commitment.to_hex()))
576                .collect::<Vec<_>>(),
577        );
578
579        const DELETE_QUERY: &str = "DELETE FROM accounts WHERE account_commitment IN rarray(?)";
580        tx.execute(DELETE_QUERY, params![commitment_params]).into_store_error()?;
581
582        // Discard each rolled-back state from the in-memory forest,
583        // restoring the previous roots for each account.
584        for (account_id, _) in discarded_states {
585            smt_forest.discard_roots(*account_id);
586        }
587
588        Ok(())
589    }
590
591    /// Replaces the account state with a completely new one from the network.
592    ///
593    /// Inserts all vault/storage data and atomically replaces the SMT forest roots.
594    pub(crate) fn update_account_state(
595        tx: &Transaction<'_>,
596        smt_forest: &mut AccountSmtForest,
597        new_account_state: &Account,
598    ) -> Result<(), StoreError> {
599        smt_forest.insert_and_register_account_state(
600            new_account_state.id(),
601            new_account_state.vault(),
602            new_account_state.storage(),
603        )?;
604        Self::insert_storage_slots(
605            tx,
606            new_account_state.storage().to_commitment(),
607            new_account_state.storage().slots().iter(),
608        )?;
609        Self::insert_assets(
610            tx,
611            new_account_state.vault().root(),
612            new_account_state.vault().assets(),
613        )?;
614        Self::insert_account_header(tx, &new_account_state.into(), None)?;
615
616        Ok(())
617    }
618
619    /// Locks the account if the mismatched digest doesn't belong to a previous account state (stale
620    /// data).
621    pub(crate) fn lock_account_on_unexpected_commitment(
622        tx: &Transaction<'_>,
623        account_id: &AccountId,
624        mismatched_digest: &Word,
625    ) -> Result<(), StoreError> {
626        // Mismatched digests may be due to stale network data. If the mismatched digest is
627        // tracked in the db and corresponds to the mismatched account, it means we
628        // got a past update and shouldn't lock the account.
629        const QUERY: &str = "UPDATE accounts SET locked = true WHERE id = :account_id AND NOT EXISTS (SELECT 1 FROM accounts WHERE id = :account_id AND account_commitment = :digest)";
630        tx.execute(
631            QUERY,
632            named_params! {
633                ":account_id": account_id.to_hex(),
634                ":digest": mismatched_digest.to_string()
635            },
636        )
637        .into_store_error()?;
638        Ok(())
639    }
640
641    // HELPERS
642    // --------------------------------------------------------------------------------------------
643
644    /// Inserts the new final account header and copies over the previous account state.
645    fn copy_account_state(
646        tx: &Transaction<'_>,
647        init_account_header: &AccountHeader,
648        final_account_header: &AccountHeader,
649    ) -> Result<(), StoreError> {
650        Self::insert_account_header(tx, final_account_header, None)?;
651
652        if init_account_header.vault_root() != final_account_header.vault_root() {
653            const VAULT_QUERY: &str = "
654                INSERT OR IGNORE INTO account_assets (
655                    root,
656                    vault_key,
657                    faucet_id_prefix,
658                    asset
659                )
660                SELECT
661                    ?, --new root
662                    vault_key,
663                    faucet_id_prefix,
664                    asset
665                FROM account_assets
666                WHERE root = (SELECT vault_root FROM accounts WHERE account_commitment = ?)
667                ";
668            tx.execute(
669                VAULT_QUERY,
670                params![
671                    final_account_header.vault_root().to_hex(),
672                    init_account_header.commitment().to_hex()
673                ],
674            )
675            .into_store_error()?;
676        }
677
678        if init_account_header.storage_commitment() != final_account_header.storage_commitment() {
679            const STORAGE_QUERY: &str = "
680                INSERT OR IGNORE INTO account_storage (
681                    commitment,
682                    slot_name,
683                    slot_value,
684                    slot_type
685                )
686                SELECT
687                    ?, -- new commitment
688                    slot_name,
689                    slot_value,
690                    slot_type
691                FROM account_storage
692                WHERE commitment = (SELECT storage_commitment FROM accounts WHERE account_commitment = ?)
693                ";
694
695            tx.execute(
696                STORAGE_QUERY,
697                params![
698                    final_account_header.storage_commitment().to_hex(),
699                    init_account_header.commitment().to_hex()
700                ],
701            )
702            .into_store_error()?;
703        }
704
705        Ok(())
706    }
707
708    /// Inserts a new account record into the database.
709    fn insert_account_header(
710        tx: &Transaction<'_>,
711        account: &AccountHeader,
712        account_seed: Option<Word>,
713    ) -> Result<(), StoreError> {
714        let id: String = account.id().to_hex();
715        let code_commitment = account.code_commitment().to_string();
716        let storage_commitment = account.storage_commitment().to_string();
717        let vault_root = account.vault_root().to_string();
718        let nonce = u64_to_value(account.nonce().as_int());
719        let commitment = account.commitment().to_string();
720
721        let account_seed = account_seed.map(|seed| seed.to_bytes());
722
723        const QUERY: &str = insert_sql!(
724            accounts {
725                id,
726                code_commitment,
727                storage_commitment,
728                vault_root,
729                nonce,
730                account_seed,
731                account_commitment,
732                locked
733            } | REPLACE
734        );
735
736        tx.execute(
737            QUERY,
738            params![
739                id,
740                code_commitment,
741                storage_commitment,
742                vault_root,
743                nonce,
744                account_seed,
745                commitment,
746                false,
747            ],
748        )
749        .into_store_error()?;
750
751        Self::insert_tracked_account_id_tx(tx, account.id())?;
752        Ok(())
753    }
754
755    fn insert_tracked_account_id_tx(
756        tx: &Transaction<'_>,
757        account_id: AccountId,
758    ) -> Result<(), StoreError> {
759        const QUERY: &str = insert_sql!(tracked_accounts { id } | IGNORE);
760        tx.execute(QUERY, params![account_id.to_hex()]).into_store_error()?;
761        Ok(())
762    }
763
764    fn insert_address_internal(
765        tx: &Transaction<'_>,
766        address: &Address,
767        account_id: AccountId,
768    ) -> Result<(), StoreError> {
769        const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
770        let serialized_address = address.to_bytes();
771        tx.execute(QUERY, params![serialized_address, account_id.to_hex(),])
772            .into_store_error()?;
773
774        Ok(())
775    }
776
777    fn remove_address_internal(tx: &Transaction<'_>, address: &Address) -> Result<(), StoreError> {
778        let serialized_address = address.to_bytes();
779
780        const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
781        tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
782
783        Ok(())
784    }
785}