breez_sdk_liquid/persist/
cache.rs

1use 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            // insert a mock updated field so that merging with incoming data works as expected
154            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}