breez_sdk_liquid/persist/
cache.rs1use anyhow::Result;
2use rusqlite::{OptionalExtension, Transaction, TransactionBehavior};
3use std::str::FromStr;
4
5use crate::model::GetInfoResponse;
6use crate::sync::model::{data::LAST_DERIVATION_INDEX_DATA_ID, RecordType};
7
8use super::{BlockchainInfo, Persister, WalletInfo};
9
10const KEY_WALLET_INFO: &str = "wallet_info";
11const KEY_BLOCKCHAIN_INFO: &str = "blockchain_info";
12const KEY_SWAPPER_PROXY_URL: &str = "swapper_proxy_url";
13const KEY_IS_FIRST_SYNC_COMPLETE: &str = "is_first_sync_complete";
14const KEY_WEBHOOK_URL: &str = "webhook_url";
15pub(crate) const KEY_LAST_DERIVATION_INDEX: &str = "last_derivation_index";
16const KEY_LAST_SCANNED_DERIVATION_INDEX: &str = "last_scanned_derivation_index";
17
18impl Persister {
19 fn get_cached_item_inner(tx: &Transaction, key: &str) -> Result<Option<String>> {
20 let res = tx.query_row(
21 "SELECT value FROM cached_items WHERE key = ?1",
22 [key],
23 |row| row.get(0),
24 );
25 Ok(res.ok())
26 }
27
28 pub(crate) fn update_cached_item_inner(
29 tx: &Transaction,
30 key: &str,
31 value: String,
32 ) -> Result<()> {
33 tx.execute(
34 "INSERT OR REPLACE INTO cached_items (key, value) VALUES (?1,?2)",
35 (key, value),
36 )?;
37 Ok(())
38 }
39
40 pub fn delete_cached_item_inner(tx: &Transaction, key: &str) -> Result<()> {
41 tx.execute("DELETE FROM cached_items WHERE key = ?1", [key])?;
42 Ok(())
43 }
44
45 pub fn get_cached_item(&self, key: &str) -> Result<Option<String>> {
46 let mut con = self.get_connection()?;
47 let tx = con.transaction()?;
48 let res = Self::get_cached_item_inner(&tx, key);
49 tx.commit()?;
50 res
51 }
52
53 pub fn update_cached_item(&self, key: &str, value: String) -> Result<()> {
54 let mut con = self.get_connection()?;
55 let tx = con.transaction()?;
56 let res = Self::update_cached_item_inner(&tx, key, value);
57 tx.commit()?;
58 res
59 }
60
61 pub fn delete_cached_item(&self, key: &str) -> Result<()> {
62 let mut con = self.get_connection()?;
63 let tx = con.transaction()?;
64 let res = Self::delete_cached_item_inner(&tx, key);
65 tx.commit()?;
66 res
67 }
68
69 pub fn set_wallet_info(&self, info: &WalletInfo) -> Result<()> {
70 let serialized_info = serde_json::to_string(info)?;
71 self.update_cached_item(KEY_WALLET_INFO, serialized_info)
72 }
73
74 pub fn set_blockchain_info(&self, info: &BlockchainInfo) -> Result<()> {
75 let serialized_info = serde_json::to_string(info)?;
76 self.update_cached_item(KEY_BLOCKCHAIN_INFO, serialized_info)
77 }
78
79 pub fn get_info(&self) -> Result<Option<GetInfoResponse>> {
80 let con = self.get_connection()?;
81
82 let info: Option<(Option<String>, Option<String>)> = con
83 .query_row(
84 &format!(
85 "
86 SELECT
87 c1.value AS wallet_info,
88 COALESCE(c2.value, NULL) AS blockchain_info
89 FROM (SELECT value FROM cached_items WHERE key = '{KEY_WALLET_INFO}') c1
90 LEFT JOIN (SELECT value FROM cached_items WHERE key = '{KEY_BLOCKCHAIN_INFO}') c2
91 "
92 ),
93 [],
94 |row| Ok((row.get(0)?, row.get(1)?)),
95 )
96 .optional()?;
97
98 match info {
99 Some((Some(wallet_info), blockchain_info)) => {
100 let wallet_info = serde_json::from_str(&wallet_info)?;
101 let blockchain_info = blockchain_info
102 .and_then(|info| serde_json::from_str(&info).ok())
103 .unwrap_or_default();
104 Ok(Some(GetInfoResponse {
105 wallet_info,
106 blockchain_info,
107 }))
108 }
109 _ => Ok(None),
110 }
111 }
112
113 pub fn set_swapper_proxy_url(&self, swapper_proxy_url: String) -> Result<()> {
114 self.update_cached_item(KEY_SWAPPER_PROXY_URL, swapper_proxy_url)
115 }
116
117 #[allow(dead_code)]
118 pub fn remove_swapper_proxy_url(&self) -> Result<()> {
119 self.delete_cached_item(KEY_SWAPPER_PROXY_URL)
120 }
121
122 pub fn get_swapper_proxy_url(&self) -> Result<Option<String>> {
123 self.get_cached_item(KEY_SWAPPER_PROXY_URL)
124 }
125
126 pub fn set_is_first_sync_complete(&self, complete: bool) -> Result<()> {
127 self.update_cached_item(KEY_IS_FIRST_SYNC_COMPLETE, complete.to_string())
128 }
129
130 pub fn get_is_first_sync_complete(&self) -> Result<Option<bool>> {
131 self.get_cached_item(KEY_IS_FIRST_SYNC_COMPLETE)
132 .map(|maybe_str| maybe_str.and_then(|val_str| bool::from_str(&val_str).ok()))
133 }
134
135 pub fn set_webhook_url(&self, webhook_url: String) -> Result<()> {
136 self.update_cached_item(KEY_WEBHOOK_URL, webhook_url)
137 }
138
139 pub fn remove_webhook_url(&self) -> Result<()> {
140 self.delete_cached_item(KEY_WEBHOOK_URL)
141 }
142
143 pub fn get_webhook_url(&self) -> Result<Option<String>> {
144 self.get_cached_item(KEY_WEBHOOK_URL)
145 }
146
147 pub fn set_last_derivation_index_inner(&self, tx: &Transaction, index: u32) -> Result<()> {
148 Self::update_cached_item_inner(tx, KEY_LAST_DERIVATION_INDEX, index.to_string())?;
149 self.commit_outgoing(
150 tx,
151 LAST_DERIVATION_INDEX_DATA_ID,
152 RecordType::LastDerivationIndex,
153 Some(vec![LAST_DERIVATION_INDEX_DATA_ID.to_string()]),
155 )
156 }
157
158 pub fn set_last_derivation_index(&self, index: u32) -> Result<()> {
159 let mut con = self.get_connection()?;
160 let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
161 self.set_last_derivation_index_inner(&tx, index)?;
162 tx.commit()?;
163 self.trigger_sync();
164 Ok(())
165 }
166
167 pub fn get_last_derivation_index(&self) -> Result<Option<u32>> {
168 self.get_cached_item(KEY_LAST_DERIVATION_INDEX)
169 .map(|maybe_str| maybe_str.and_then(|str| str.as_str().parse::<u32>().ok()))
170 }
171
172 pub fn next_derivation_index(&self) -> Result<Option<u32>> {
173 let mut con = self.get_connection()?;
174 let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
175 let res = match Self::get_cached_item_inner(&tx, KEY_LAST_DERIVATION_INDEX)? {
176 Some(last_index_str) => {
177 let next_index = last_index_str
178 .as_str()
179 .parse::<u32>()
180 .map(|index| index + 1)?;
181 self.set_last_derivation_index_inner(&tx, next_index)?;
182 Some(next_index)
183 }
184 None => None,
185 };
186 tx.commit()?;
187 self.trigger_sync();
188 Ok(res)
189 }
190
191 pub fn set_last_scanned_derivation_index(&self, index: u32) -> Result<()> {
192 let mut con = self.get_connection()?;
193 let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
194 Self::update_cached_item_inner(&tx, KEY_LAST_SCANNED_DERIVATION_INDEX, index.to_string())?;
195 tx.commit()?;
196 Ok(())
197 }
198
199 pub fn get_last_scanned_derivation_index(&self) -> Result<Option<u32>> {
200 self.get_cached_item(KEY_LAST_SCANNED_DERIVATION_INDEX)
201 .map(|maybe_str| maybe_str.and_then(|str| str.as_str().parse::<u32>().ok()))
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use anyhow::Result;
208
209 use crate::test_utils::persist::create_persister;
210
211 #[cfg(feature = "browser-tests")]
212 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
213
214 #[sdk_macros::test_all]
215 fn test_cached_items() -> Result<()> {
216 create_persister!(persister);
217
218 persister.update_cached_item("key1", "val1".to_string())?;
219 let item_value = persister.get_cached_item("key1")?;
220 assert_eq!(item_value, Some("val1".to_string()));
221
222 persister.delete_cached_item("key1")?;
223 let item_value = persister.get_cached_item("key1")?;
224 assert_eq!(item_value, None);
225
226 Ok(())
227 }
228
229 #[sdk_macros::test_all]
230 fn test_get_last_derivation_index() -> Result<()> {
231 create_persister!(persister);
232
233 let maybe_last_index = persister.get_last_derivation_index()?;
234 assert!(maybe_last_index.is_none());
235
236 persister.set_last_derivation_index(50)?;
237
238 let maybe_last_index = persister.get_last_derivation_index()?;
239 assert!(maybe_last_index.is_some());
240 assert_eq!(maybe_last_index, Some(50));
241
242 persister.set_last_derivation_index(51)?;
243
244 let maybe_last_index = persister.get_last_derivation_index()?;
245 assert!(maybe_last_index.is_some());
246 assert_eq!(maybe_last_index, Some(51));
247
248 Ok(())
249 }
250
251 #[sdk_macros::test_all]
252 fn test_next_derivation_index() -> Result<()> {
253 create_persister!(persister);
254
255 let maybe_next_index = persister.next_derivation_index()?;
256 assert!(maybe_next_index.is_none());
257
258 persister.set_last_derivation_index(50)?;
259
260 let maybe_next_index = persister.next_derivation_index()?;
261 assert!(maybe_next_index.is_some());
262 assert_eq!(maybe_next_index, Some(51));
263
264 let maybe_last_index = persister.get_last_derivation_index()?;
265 assert!(maybe_last_index.is_some());
266 assert_eq!(maybe_last_index, Some(51));
267
268 persister.set_last_derivation_index(52)?;
269
270 let maybe_next_index = persister.next_derivation_index()?;
271 assert!(maybe_next_index.is_some());
272 assert_eq!(maybe_next_index, Some(53));
273
274 let maybe_next_index = persister.next_derivation_index()?;
275 assert!(maybe_next_index.is_some());
276 assert_eq!(maybe_next_index, Some(54));
277
278 let maybe_last_index = persister.get_last_derivation_index()?;
279 assert!(maybe_last_index.is_some());
280 assert_eq!(maybe_last_index, Some(54));
281
282 Ok(())
283 }
284}