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 update_blockchain_info(&self, liquid_tip: u32, bitcoin_tip: Option<u32>) -> Result<()> {
75 let info = match bitcoin_tip {
76 Some(bitcoin_tip) => BlockchainInfo {
77 liquid_tip,
78 bitcoin_tip,
79 },
80 None => {
81 let current_tip = self
82 .get_cached_item(KEY_BLOCKCHAIN_INFO)?
83 .and_then(|info| serde_json::from_str::<BlockchainInfo>(&info).ok())
84 .map(|info| info.bitcoin_tip)
85 .unwrap_or(0);
86 BlockchainInfo {
87 liquid_tip,
88 bitcoin_tip: current_tip,
89 }
90 }
91 };
92
93 let serialized_info = serde_json::to_string(&info)?;
94 self.update_cached_item(KEY_BLOCKCHAIN_INFO, serialized_info)
95 }
96
97 pub fn get_info(&self) -> Result<Option<GetInfoResponse>> {
98 let con = self.get_connection()?;
99
100 let info: Option<(Option<String>, Option<String>)> = con
101 .query_row(
102 &format!(
103 "
104 SELECT
105 c1.value AS wallet_info,
106 COALESCE(c2.value, NULL) AS blockchain_info
107 FROM (SELECT value FROM cached_items WHERE key = '{KEY_WALLET_INFO}') c1
108 LEFT JOIN (SELECT value FROM cached_items WHERE key = '{KEY_BLOCKCHAIN_INFO}') c2
109 "
110 ),
111 [],
112 |row| Ok((row.get(0)?, row.get(1)?)),
113 )
114 .optional()?;
115
116 match info {
117 Some((Some(wallet_info), blockchain_info)) => {
118 let wallet_info = serde_json::from_str(&wallet_info)?;
119 let blockchain_info = blockchain_info
120 .and_then(|info| serde_json::from_str(&info).ok())
121 .unwrap_or_default();
122 Ok(Some(GetInfoResponse {
123 wallet_info,
124 blockchain_info,
125 }))
126 }
127 _ => Ok(None),
128 }
129 }
130
131 pub fn set_swapper_proxy_url(&self, swapper_proxy_url: String) -> Result<()> {
132 self.update_cached_item(KEY_SWAPPER_PROXY_URL, swapper_proxy_url)
133 }
134
135 #[allow(dead_code)]
136 pub fn remove_swapper_proxy_url(&self) -> Result<()> {
137 self.delete_cached_item(KEY_SWAPPER_PROXY_URL)
138 }
139
140 pub fn get_swapper_proxy_url(&self) -> Result<Option<String>> {
141 self.get_cached_item(KEY_SWAPPER_PROXY_URL)
142 }
143
144 pub fn set_is_first_sync_complete(&self, complete: bool) -> Result<()> {
145 self.update_cached_item(KEY_IS_FIRST_SYNC_COMPLETE, complete.to_string())
146 }
147
148 pub fn get_is_first_sync_complete(&self) -> Result<Option<bool>> {
149 self.get_cached_item(KEY_IS_FIRST_SYNC_COMPLETE)
150 .map(|maybe_str| maybe_str.and_then(|val_str| bool::from_str(&val_str).ok()))
151 }
152
153 pub fn set_webhook_url(&self, webhook_url: String) -> Result<()> {
154 self.update_cached_item(KEY_WEBHOOK_URL, webhook_url)
155 }
156
157 pub fn remove_webhook_url(&self) -> Result<()> {
158 self.delete_cached_item(KEY_WEBHOOK_URL)
159 }
160
161 pub fn get_webhook_url(&self) -> Result<Option<String>> {
162 self.get_cached_item(KEY_WEBHOOK_URL)
163 }
164
165 pub fn set_last_derivation_index_inner(&self, tx: &Transaction, index: u32) -> Result<()> {
166 Self::update_cached_item_inner(tx, KEY_LAST_DERIVATION_INDEX, index.to_string())?;
167 self.commit_outgoing(
168 tx,
169 LAST_DERIVATION_INDEX_DATA_ID,
170 RecordType::LastDerivationIndex,
171 Some(vec![LAST_DERIVATION_INDEX_DATA_ID.to_string()]),
173 )
174 }
175
176 pub fn set_last_derivation_index(&self, index: u32) -> Result<()> {
177 let mut con = self.get_connection()?;
178 let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
179 self.set_last_derivation_index_inner(&tx, index)?;
180 tx.commit()?;
181 self.trigger_sync();
182 Ok(())
183 }
184
185 pub fn get_last_derivation_index(&self) -> Result<Option<u32>> {
186 self.get_cached_item(KEY_LAST_DERIVATION_INDEX)
187 .map(|maybe_str| maybe_str.and_then(|str| str.as_str().parse::<u32>().ok()))
188 }
189
190 pub fn next_derivation_index(&self) -> Result<Option<u32>> {
191 let mut con = self.get_connection()?;
192 let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
193 let res = match Self::get_cached_item_inner(&tx, KEY_LAST_DERIVATION_INDEX)? {
194 Some(last_index_str) => {
195 let next_index = last_index_str
196 .as_str()
197 .parse::<u32>()
198 .map(|index| index + 1)?;
199 self.set_last_derivation_index_inner(&tx, next_index)?;
200 Some(next_index)
201 }
202 None => None,
203 };
204 tx.commit()?;
205 self.trigger_sync();
206 Ok(res)
207 }
208
209 pub fn set_last_scanned_derivation_index(&self, index: u32) -> Result<()> {
210 let mut con = self.get_connection()?;
211 let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
212 Self::update_cached_item_inner(&tx, KEY_LAST_SCANNED_DERIVATION_INDEX, index.to_string())?;
213 tx.commit()?;
214 Ok(())
215 }
216
217 pub fn get_last_scanned_derivation_index(&self) -> Result<Option<u32>> {
218 self.get_cached_item(KEY_LAST_SCANNED_DERIVATION_INDEX)
219 .map(|maybe_str| maybe_str.and_then(|str| str.as_str().parse::<u32>().ok()))
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use anyhow::Result;
226
227 use crate::test_utils::persist::create_persister;
228
229 #[cfg(feature = "browser-tests")]
230 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
231
232 #[sdk_macros::test_all]
233 fn test_cached_items() -> Result<()> {
234 create_persister!(persister);
235
236 persister.update_cached_item("key1", "val1".to_string())?;
237 let item_value = persister.get_cached_item("key1")?;
238 assert_eq!(item_value, Some("val1".to_string()));
239
240 persister.delete_cached_item("key1")?;
241 let item_value = persister.get_cached_item("key1")?;
242 assert_eq!(item_value, None);
243
244 Ok(())
245 }
246
247 #[sdk_macros::test_all]
248 fn test_get_last_derivation_index() -> Result<()> {
249 create_persister!(persister);
250
251 let maybe_last_index = persister.get_last_derivation_index()?;
252 assert!(maybe_last_index.is_none());
253
254 persister.set_last_derivation_index(50)?;
255
256 let maybe_last_index = persister.get_last_derivation_index()?;
257 assert!(maybe_last_index.is_some());
258 assert_eq!(maybe_last_index, Some(50));
259
260 persister.set_last_derivation_index(51)?;
261
262 let maybe_last_index = persister.get_last_derivation_index()?;
263 assert!(maybe_last_index.is_some());
264 assert_eq!(maybe_last_index, Some(51));
265
266 Ok(())
267 }
268
269 #[sdk_macros::test_all]
270 fn test_next_derivation_index() -> Result<()> {
271 create_persister!(persister);
272
273 let maybe_next_index = persister.next_derivation_index()?;
274 assert!(maybe_next_index.is_none());
275
276 persister.set_last_derivation_index(50)?;
277
278 let maybe_next_index = persister.next_derivation_index()?;
279 assert!(maybe_next_index.is_some());
280 assert_eq!(maybe_next_index, Some(51));
281
282 let maybe_last_index = persister.get_last_derivation_index()?;
283 assert!(maybe_last_index.is_some());
284 assert_eq!(maybe_last_index, Some(51));
285
286 persister.set_last_derivation_index(52)?;
287
288 let maybe_next_index = persister.next_derivation_index()?;
289 assert!(maybe_next_index.is_some());
290 assert_eq!(maybe_next_index, Some(53));
291
292 let maybe_next_index = persister.next_derivation_index()?;
293 assert!(maybe_next_index.is_some());
294 assert_eq!(maybe_next_index, Some(54));
295
296 let maybe_last_index = persister.get_last_derivation_index()?;
297 assert!(maybe_last_index.is_some());
298 assert_eq!(maybe_last_index, Some(54));
299
300 Ok(())
301 }
302}