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 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_maps,
49 query_storage_slots,
50 query_storage_values,
51 query_vault_assets,
52};
53use crate::smt_forest::AccountSmtForest;
54use crate::sql_error::SqlResultExt;
55use crate::sync::{add_note_tag_tx, remove_note_tag_tx};
56use crate::{SqliteStore, column_value_as_u64, insert_sql, subst, u64_to_value};
57
58impl SqliteStore {
59 pub(crate) fn get_account_ids(conn: &mut Connection) -> Result<Vec<AccountId>, StoreError> {
63 const QUERY: &str = "SELECT id FROM tracked_accounts";
64
65 conn.prepare_cached(QUERY)
66 .into_store_error()?
67 .query_map([], |row| row.get(0))
68 .expect("no binding parameters used in query")
69 .map(|result| {
70 let id: String = result.map_err(|e| StoreError::ParsingError(e.to_string()))?;
71 Ok(AccountId::from_hex(&id).expect("account id is valid"))
72 })
73 .collect::<Result<Vec<AccountId>, StoreError>>()
74 }
75
76 pub(crate) fn get_account_headers(
77 conn: &mut Connection,
78 ) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
79 const QUERY: &str = "
80 SELECT
81 a.id,
82 a.nonce,
83 a.vault_root,
84 a.storage_commitment,
85 a.code_commitment,
86 a.account_seed,
87 a.locked
88 FROM accounts AS a
89 JOIN (
90 SELECT id, MAX(nonce) AS nonce
91 FROM accounts
92 GROUP BY id
93 ) AS latest
94 ON a.id = latest.id
95 AND a.nonce = latest.nonce
96 ORDER BY a.id;
97 ";
98
99 conn.prepare_cached(QUERY)
100 .into_store_error()?
101 .query_map(params![], |row| {
102 let id: String = row.get(0)?;
103 let nonce: u64 = column_value_as_u64(row, 1)?;
104 let vault_root: String = row.get(2)?;
105 let storage_commitment: String = row.get(3)?;
106 let code_commitment: String = row.get(4)?;
107 let account_seed: Option<Vec<u8>> = row.get(5)?;
108 let locked: bool = row.get(6)?;
109
110 Ok(SerializedHeaderData {
111 id,
112 nonce,
113 vault_root,
114 storage_commitment,
115 code_commitment,
116 account_seed,
117 locked,
118 })
119 })
120 .into_store_error()?
121 .map(|result| parse_accounts(result.into_store_error()?))
122 .collect::<Result<Vec<(AccountHeader, AccountStatus)>, StoreError>>()
123 }
124
125 pub(crate) fn get_account_header(
126 conn: &mut Connection,
127 account_id: AccountId,
128 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
129 Ok(query_account_headers(
130 conn,
131 "id = ? ORDER BY nonce DESC LIMIT 1",
132 params![account_id.to_hex()],
133 )?
134 .pop())
135 }
136
137 pub(crate) fn get_account_header_by_commitment(
138 conn: &mut Connection,
139 account_commitment: Word,
140 ) -> Result<Option<AccountHeader>, StoreError> {
141 let account_commitment_str: String = account_commitment.to_string();
142 Ok(
143 query_account_headers(conn, "account_commitment = ?", params![account_commitment_str])?
144 .pop()
145 .map(|(header, _)| header),
146 )
147 }
148
149 pub(crate) fn get_account(
151 conn: &mut Connection,
152 account_id: AccountId,
153 ) -> Result<Option<AccountRecord>, StoreError> {
154 let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
155 return Ok(None);
156 };
157
158 let assets = query_vault_assets(conn, "root = ?", params![header.vault_root().to_hex()])?;
159 let vault = AssetVault::new(&assets)?;
160
161 let slots = query_storage_slots(
162 conn,
163 "commitment = ?",
164 params![header.storage_commitment().to_hex()],
165 )?;
166
167 let storage = AccountStorage::new(slots)?;
168
169 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
170 return Ok(None);
171 };
172
173 let account = Account::new_unchecked(
174 header.id(),
175 vault,
176 storage,
177 account_code,
178 header.nonce(),
179 status.seed().copied(),
180 );
181
182 let addresses = query_account_addresses(conn, header.id())?;
183 let account_data = AccountRecordData::Full(account);
184 Ok(Some(AccountRecord::new(account_data, status, addresses)))
185 }
186
187 pub(crate) fn get_minimal_partial_account(
189 conn: &mut Connection,
190 smt_forest: &Arc<RwLock<AccountSmtForest>>,
191 account_id: AccountId,
192 ) -> Result<Option<AccountRecord>, StoreError> {
193 let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
194 return Ok(None);
195 };
196
197 let partial_vault = PartialVault::new(header.vault_root());
199
200 let mut storage_header = Vec::new();
202 let mut maps = vec![];
203
204 let storage_values = query_storage_values(
205 conn,
206 "commitment = ?",
207 params![header.storage_commitment().to_hex()],
208 )?;
209
210 let map_roots: Vec<Value> = storage_values
212 .iter()
213 .filter(|(_, (slot_type, _))| *slot_type == StorageSlotType::Map)
214 .map(|(_, (_, value))| Value::from(value.to_hex()))
215 .collect();
216
217 let mut all_storage_maps = if map_roots.is_empty() {
219 BTreeMap::new()
220 } else {
221 query_storage_maps(conn, "root IN rarray(?)", [Rc::new(map_roots)])?
222 };
223
224 for (slot_name, (slot_type, value)) in storage_values {
225 storage_header.push(StorageSlotHeader::new(slot_name.clone(), slot_type, value));
226 if slot_type == StorageSlotType::Map {
227 let mut partial_storage_map = PartialStorageMap::new(value);
228
229 if let Some(map) = all_storage_maps.remove(&value) {
230 let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
231 for (k, _v) in map.entries() {
232 let witness = smt_forest.get_storage_map_item_witness(value, *k)?;
233 partial_storage_map.add(witness).map_err(StoreError::MerkleStoreError)?;
234 }
235 }
236
237 maps.push(partial_storage_map);
238 }
239 }
240 storage_header.sort_by_key(StorageSlotHeader::id);
241 let storage_header =
242 AccountStorageHeader::new(storage_header).map_err(StoreError::AccountError)?;
243 let partial_storage =
244 PartialStorage::new(storage_header, maps).map_err(StoreError::AccountError)?;
245
246 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
247 return Ok(None);
248 };
249
250 let partial_account = PartialAccount::new(
251 header.id(),
252 header.nonce(),
253 account_code,
254 partial_storage,
255 partial_vault,
256 status.seed().copied(),
257 )?;
258 let account_record_data = AccountRecordData::Partial(partial_account);
259 let addresses = query_account_addresses(conn, header.id())?;
260 Ok(Some(AccountRecord::new(account_record_data, status, addresses)))
261 }
262
263 pub fn get_foreign_account_code(
264 conn: &mut Connection,
265 account_ids: Vec<AccountId>,
266 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
267 let params: Vec<Value> =
268 account_ids.into_iter().map(|id| Value::from(id.to_hex())).collect();
269 const QUERY: &str = "
270 SELECT account_id, code
271 FROM foreign_account_code JOIN account_code ON foreign_account_code.code_commitment = account_code.commitment
272 WHERE account_id IN rarray(?)";
273
274 conn.prepare_cached(QUERY)
275 .into_store_error()?
276 .query_map([Rc::new(params)], |row| Ok((row.get(0)?, row.get(1)?)))
277 .expect("no binding parameters used in query")
278 .map(|result| {
279 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
280 |(id, code): (String, Vec<u8>)| {
281 Ok((
282 AccountId::from_hex(&id).map_err(|err| {
283 StoreError::AccountError(
284 AccountError::FinalAccountHeaderIdParsingFailed(err),
285 )
286 })?,
287 AccountCode::from_bytes(&code).map_err(StoreError::AccountError)?,
288 ))
289 },
290 )
291 })
292 .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
293 }
294
295 pub fn get_account_vault(
297 conn: &Connection,
298 account_id: AccountId,
299 ) -> Result<AssetVault, StoreError> {
300 let assets = query_vault_assets(
301 conn,
302 "root = (SELECT vault_root FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1)",
303 params![account_id.to_hex()],
304 )?;
305
306 Ok(AssetVault::new(&assets)?)
307 }
308
309 pub fn get_account_storage(
311 conn: &Connection,
312 account_id: AccountId,
313 filter: &AccountStorageFilter,
314 ) -> Result<AccountStorage, StoreError> {
315 let (where_clause, params) = match filter {
316 AccountStorageFilter::All => (
317 "commitment = (SELECT storage_commitment FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1)",
318 params![account_id.to_hex()],
319 ),
320 AccountStorageFilter::Root(root) => (
321 "commitment = (SELECT storage_commitment FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1) AND slot_value = ?",
322 params![account_id.to_hex(), root.to_hex()],
323 ),
324 AccountStorageFilter::SlotName(slot_name) => (
325 "commitment = (SELECT storage_commitment FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1) AND slot_name = ?",
326 params![account_id.to_hex(), slot_name.to_string()],
327 ),
328 };
329
330 let slots = query_storage_slots(conn, where_clause, params)?;
331
332 Ok(AccountStorage::new(slots)?)
333 }
334
335 pub(crate) fn get_account_asset(
338 conn: &mut Connection,
339 smt_forest: &Arc<RwLock<AccountSmtForest>>,
340 account_id: AccountId,
341 vault_key: AssetVaultKey,
342 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
343 let header = Self::get_account_header(conn, account_id)?
344 .ok_or(StoreError::AccountDataNotFound(account_id))?
345 .0;
346
347 let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
348 match smt_forest.get_asset_and_witness(header.vault_root(), vault_key) {
349 Ok((asset, witness)) => Ok(Some((asset, witness))),
350 Err(StoreError::MerkleStoreError(MerkleError::UntrackedKey(_))) => Ok(None),
351 Err(err) => Err(err),
352 }
353 }
354
355 pub(crate) fn get_account_map_item(
358 conn: &mut Connection,
359 smt_forest: &Arc<RwLock<AccountSmtForest>>,
360 account_id: AccountId,
361 slot_name: StorageSlotName,
362 key: Word,
363 ) -> Result<(Word, StorageMapWitness), StoreError> {
364 let header = Self::get_account_header(conn, account_id)?
365 .ok_or(StoreError::AccountDataNotFound(account_id))?
366 .0;
367
368 let mut storage_values = query_storage_values(
369 conn,
370 "commitment = ? AND slot_name = ?",
371 params![header.storage_commitment().to_hex(), slot_name.to_string()],
372 )?;
373 let (slot_type, map_root) = storage_values
374 .remove(&slot_name)
375 .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))?;
376 if slot_type != StorageSlotType::Map {
377 return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name)));
378 }
379
380 let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned");
381 let witness = smt_forest.get_storage_map_item_witness(map_root, key)?;
382 let item = witness.get(&key).unwrap_or(miden_client::EMPTY_WORD);
383
384 Ok((item, witness))
385 }
386
387 pub(crate) fn get_account_addresses(
388 conn: &mut Connection,
389 account_id: AccountId,
390 ) -> Result<Vec<Address>, StoreError> {
391 query_account_addresses(conn, account_id)
392 }
393
394 pub(crate) fn insert_account(
398 conn: &mut Connection,
399 smt_forest: &Arc<RwLock<AccountSmtForest>>,
400 account: &Account,
401 initial_address: &Address,
402 ) -> Result<(), StoreError> {
403 let tx = conn.transaction().into_store_error()?;
404
405 Self::insert_account_code(&tx, account.code())?;
406
407 Self::insert_storage_slots(
408 &tx,
409 account.storage().to_commitment(),
410 account.storage().slots().iter(),
411 )?;
412
413 Self::insert_assets(&tx, account.vault().root(), account.vault().assets())?;
414 Self::insert_account_header(&tx, &account.into(), account.seed())?;
415
416 Self::insert_address(&tx, initial_address, account.id())?;
417
418 tx.commit().into_store_error()?;
419
420 let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
421 smt_forest.insert_and_register_account_state(
422 account.id(),
423 account.vault(),
424 account.storage(),
425 )?;
426
427 Ok(())
428 }
429
430 pub(crate) fn update_account(
431 conn: &mut Connection,
432 smt_forest: &Arc<RwLock<AccountSmtForest>>,
433 new_account_state: &Account,
434 ) -> Result<(), StoreError> {
435 const QUERY: &str = "SELECT id FROM accounts WHERE id = ?";
436 if conn
437 .prepare(QUERY)
438 .into_store_error()?
439 .query_map(params![new_account_state.id().to_hex()], |row| row.get(0))
440 .into_store_error()?
441 .map(|result| {
442 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
443 |id: String| {
444 AccountId::from_hex(&id).map_err(|err| {
445 StoreError::AccountError(
446 AccountError::FinalAccountHeaderIdParsingFailed(err),
447 )
448 })
449 },
450 )
451 })
452 .next()
453 .is_none()
454 {
455 return Err(StoreError::AccountDataNotFound(new_account_state.id()));
456 }
457
458 let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned");
459 let tx = conn.transaction().into_store_error()?;
460 Self::update_account_state(&tx, &mut smt_forest, new_account_state)?;
461 tx.commit().into_store_error()
462 }
463
464 pub fn upsert_foreign_account_code(
465 conn: &mut Connection,
466 account_id: AccountId,
467 code: &AccountCode,
468 ) -> Result<(), StoreError> {
469 let tx = conn.transaction().into_store_error()?;
470
471 Self::insert_account_code(&tx, code)?;
472
473 const QUERY: &str =
474 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
475
476 tx.execute(QUERY, params![account_id.to_hex(), code.commitment().to_string()])
477 .into_store_error()?;
478
479 Self::insert_account_code(&tx, code)?;
480 tx.commit().into_store_error()
481 }
482
483 pub(crate) fn insert_address(
484 tx: &Transaction<'_>,
485 address: &Address,
486 account_id: AccountId,
487 ) -> Result<(), StoreError> {
488 let derived_note_tag = address.to_note_tag();
489 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
490
491 add_note_tag_tx(tx, ¬e_tag_record)?;
492 Self::insert_address_internal(tx, address, account_id)?;
493
494 Ok(())
495 }
496
497 pub(crate) fn remove_address(
498 conn: &mut Connection,
499 address: &Address,
500 account_id: AccountId,
501 ) -> Result<(), StoreError> {
502 let derived_note_tag = address.to_note_tag();
503 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
504
505 let tx = conn.transaction().into_store_error()?;
506 remove_note_tag_tx(&tx, note_tag_record)?;
507 Self::remove_address_internal(&tx, address)?;
508
509 tx.commit().into_store_error()
510 }
511
512 pub(crate) fn insert_account_code(
514 tx: &Transaction<'_>,
515 account_code: &AccountCode,
516 ) -> Result<(), StoreError> {
517 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
518 tx.execute(QUERY, params![account_code.commitment().to_hex(), account_code.to_bytes()])
519 .into_store_error()?;
520 Ok(())
521 }
522
523 pub(crate) fn apply_account_delta(
531 tx: &Transaction<'_>,
532 smt_forest: &mut AccountSmtForest,
533 init_account_state: &AccountHeader,
534 final_account_state: &AccountHeader,
535 updated_fungible_assets: BTreeMap<AccountIdPrefix, FungibleAsset>,
536 updated_storage_maps: BTreeMap<StorageSlotName, StorageMap>,
537 delta: &AccountDelta,
538 ) -> Result<(), StoreError> {
539 Self::copy_account_state(tx, init_account_state, final_account_state)?;
542
543 Self::apply_account_vault_delta(
544 tx,
545 smt_forest,
546 init_account_state,
547 final_account_state,
548 updated_fungible_assets,
549 delta,
550 )?;
551
552 let mut final_roots = smt_forest
557 .get_roots(&init_account_state.id())
558 .cloned()
559 .ok_or(StoreError::AccountDataNotFound(init_account_state.id()))?;
560
561 if let Some(vault_root) = final_roots.first_mut() {
563 *vault_root = final_account_state.vault_root();
564 }
565
566 let updated_storage_slots = Self::apply_account_storage_delta(
567 smt_forest,
568 &mut final_roots,
569 updated_storage_maps,
570 delta,
571 )?;
572
573 Self::insert_storage_slots(
574 tx,
575 final_account_state.storage_commitment(),
576 updated_storage_slots.iter(),
577 )?;
578
579 smt_forest.stage_roots(final_account_state.id(), final_roots);
580
581 Ok(())
582 }
583
584 pub(crate) fn undo_account_state(
587 tx: &Transaction<'_>,
588 smt_forest: &mut AccountSmtForest,
589 discarded_states: &[(AccountId, Word)],
590 ) -> Result<(), StoreError> {
591 if discarded_states.is_empty() {
592 return Ok(());
593 }
594
595 let commitment_params = Rc::new(
596 discarded_states
597 .iter()
598 .map(|(_, commitment)| Value::from(commitment.to_hex()))
599 .collect::<Vec<_>>(),
600 );
601
602 const DELETE_QUERY: &str = "DELETE FROM accounts WHERE account_commitment IN rarray(?)";
603 tx.execute(DELETE_QUERY, params![commitment_params]).into_store_error()?;
604
605 for (account_id, _) in discarded_states {
608 smt_forest.discard_roots(*account_id);
609 }
610
611 Ok(())
612 }
613
614 pub(crate) fn update_account_state(
618 tx: &Transaction<'_>,
619 smt_forest: &mut AccountSmtForest,
620 new_account_state: &Account,
621 ) -> Result<(), StoreError> {
622 smt_forest.insert_and_register_account_state(
623 new_account_state.id(),
624 new_account_state.vault(),
625 new_account_state.storage(),
626 )?;
627 Self::insert_storage_slots(
628 tx,
629 new_account_state.storage().to_commitment(),
630 new_account_state.storage().slots().iter(),
631 )?;
632 Self::insert_assets(
633 tx,
634 new_account_state.vault().root(),
635 new_account_state.vault().assets(),
636 )?;
637 Self::insert_account_header(tx, &new_account_state.into(), None)?;
638
639 Ok(())
640 }
641
642 pub(crate) fn lock_account_on_unexpected_commitment(
645 tx: &Transaction<'_>,
646 account_id: &AccountId,
647 mismatched_digest: &Word,
648 ) -> Result<(), StoreError> {
649 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)";
653 tx.execute(
654 QUERY,
655 named_params! {
656 ":account_id": account_id.to_hex(),
657 ":digest": mismatched_digest.to_string()
658 },
659 )
660 .into_store_error()?;
661 Ok(())
662 }
663
664 fn copy_account_state(
669 tx: &Transaction<'_>,
670 init_account_header: &AccountHeader,
671 final_account_header: &AccountHeader,
672 ) -> Result<(), StoreError> {
673 Self::insert_account_header(tx, final_account_header, None)?;
674
675 if init_account_header.vault_root() != final_account_header.vault_root() {
676 const VAULT_QUERY: &str = "
677 INSERT OR IGNORE INTO account_assets (
678 root,
679 vault_key,
680 faucet_id_prefix,
681 asset
682 )
683 SELECT
684 ?, --new root
685 vault_key,
686 faucet_id_prefix,
687 asset
688 FROM account_assets
689 WHERE root = (SELECT vault_root FROM accounts WHERE account_commitment = ?)
690 ";
691 tx.execute(
692 VAULT_QUERY,
693 params![
694 final_account_header.vault_root().to_hex(),
695 init_account_header.commitment().to_hex()
696 ],
697 )
698 .into_store_error()?;
699 }
700
701 if init_account_header.storage_commitment() != final_account_header.storage_commitment() {
702 const STORAGE_QUERY: &str = "
703 INSERT OR IGNORE INTO account_storage (
704 commitment,
705 slot_name,
706 slot_value,
707 slot_type
708 )
709 SELECT
710 ?, -- new commitment
711 slot_name,
712 slot_value,
713 slot_type
714 FROM account_storage
715 WHERE commitment = (SELECT storage_commitment FROM accounts WHERE account_commitment = ?)
716 ";
717
718 tx.execute(
719 STORAGE_QUERY,
720 params![
721 final_account_header.storage_commitment().to_hex(),
722 init_account_header.commitment().to_hex()
723 ],
724 )
725 .into_store_error()?;
726 }
727
728 Ok(())
729 }
730
731 fn insert_account_header(
733 tx: &Transaction<'_>,
734 account: &AccountHeader,
735 account_seed: Option<Word>,
736 ) -> Result<(), StoreError> {
737 let id: String = account.id().to_hex();
738 let code_commitment = account.code_commitment().to_string();
739 let storage_commitment = account.storage_commitment().to_string();
740 let vault_root = account.vault_root().to_string();
741 let nonce = u64_to_value(account.nonce().as_int());
742 let commitment = account.commitment().to_string();
743
744 let account_seed = account_seed.map(|seed| seed.to_bytes());
745
746 const QUERY: &str = insert_sql!(
747 accounts {
748 id,
749 code_commitment,
750 storage_commitment,
751 vault_root,
752 nonce,
753 account_seed,
754 account_commitment,
755 locked
756 } | REPLACE
757 );
758
759 tx.execute(
760 QUERY,
761 params![
762 id,
763 code_commitment,
764 storage_commitment,
765 vault_root,
766 nonce,
767 account_seed,
768 commitment,
769 false,
770 ],
771 )
772 .into_store_error()?;
773
774 Self::insert_tracked_account_id_tx(tx, account.id())?;
775 Ok(())
776 }
777
778 fn insert_tracked_account_id_tx(
779 tx: &Transaction<'_>,
780 account_id: AccountId,
781 ) -> Result<(), StoreError> {
782 const QUERY: &str = insert_sql!(tracked_accounts { id } | IGNORE);
783 tx.execute(QUERY, params![account_id.to_hex()]).into_store_error()?;
784 Ok(())
785 }
786
787 fn insert_address_internal(
788 tx: &Transaction<'_>,
789 address: &Address,
790 account_id: AccountId,
791 ) -> Result<(), StoreError> {
792 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
793 let serialized_address = address.to_bytes();
794 tx.execute(QUERY, params![serialized_address, account_id.to_hex(),])
795 .into_store_error()?;
796
797 Ok(())
798 }
799
800 fn remove_address_internal(tx: &Transaction<'_>, address: &Address) -> Result<(), StoreError> {
801 let serialized_address = address.to_bytes();
802
803 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
804 tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
805
806 Ok(())
807 }
808}