breez_sdk_liquid/persist/
address.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use anyhow::Result;
use log::debug;
use rusqlite::{Connection, Row, TransactionBehavior};

use crate::{error::PaymentError, persist::where_clauses_to_string};

use super::{Persister, ReservedAddress};

impl Persister {
    pub(crate) fn next_expired_reserved_address(
        &self,
        tip: u32,
    ) -> Result<Option<ReservedAddress>> {
        let mut con = self.get_connection()?;
        let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
        // Get the next expired reserved address
        let query = Self::get_reserved_address_query(vec!["expiry_block_height < ?1".to_string()]);
        let res = match tx.query_row(&query, [tip], Self::sql_row_to_reserved_address) {
            Ok(reserved_address) => {
                // Delete the reserved address
                Self::delete_reserved_address_inner(&tx, &reserved_address.address)?;
                Some(reserved_address)
            }
            Err(_) => None,
        };
        tx.commit()?;

        Ok(res)
    }

    fn get_reserved_address_query(where_clauses: Vec<String>) -> String {
        let where_clause_str = where_clauses_to_string(where_clauses);

        format!(
            "
            SELECT
                address,
                expiry_block_height
            FROM reserved_addresses
            {where_clause_str}
            ORDER BY expiry_block_height ASC
            LIMIT 1
        "
        )
    }

    pub(crate) fn insert_or_update_reserved_address(
        &self,
        address: &str,
        expiry_block_height: u32,
    ) -> Result<(), PaymentError> {
        let con = self.get_connection()?;
        con.execute(
            "INSERT OR REPLACE INTO reserved_addresses (
           address,
           expiry_block_height
        )
        VALUES (?, ?)
        ",
            (&address, expiry_block_height),
        )?;
        debug!(
            "Reserved address {} until block height {}",
            address, expiry_block_height
        );

        Ok(())
    }

    pub(crate) fn delete_reserved_address(&self, address: &str) -> Result<(), PaymentError> {
        let mut con = self.get_connection()?;
        let tx = con.transaction()?;
        Self::delete_reserved_address_inner(&tx, address)?;
        tx.commit()?;

        Ok(())
    }

    pub(crate) fn delete_reserved_address_inner(
        tx: &Connection,
        address: &str,
    ) -> Result<(), PaymentError> {
        tx.execute(
            "DELETE FROM reserved_addresses WHERE address = ?",
            [address],
        )?;

        Ok(())
    }

    fn sql_row_to_reserved_address(row: &Row) -> rusqlite::Result<ReservedAddress> {
        Ok(ReservedAddress {
            address: row.get(0)?,
            expiry_block_height: row.get(1)?,
        })
    }
}

#[cfg(test)]
mod tests {
    use anyhow::Result;

    use crate::test_utils::persist::create_persister;

    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

    #[sdk_macros::test_all]
    fn test_next_expired_reserved_address() -> Result<()> {
        create_persister!(storage);
        let address = "tlq1pq2amlulhea6ltq7x3eu9atsc2nnrer7yt7xve363zxedqwu2mk6ctcyv9awl8xf28cythreqklt5q0qqwsxzlm6wu4z6d574adl9zh2zmr0h85gt534n";

        storage.insert_or_update_reserved_address(address, 100)?;

        let maybe_reserved_address = storage.next_expired_reserved_address(99)?;
        // Under the expiry, not popped
        assert!(maybe_reserved_address.is_none());

        let maybe_reserved_address = storage.next_expired_reserved_address(100)?;
        // Equal to expiry, not popped
        assert!(maybe_reserved_address.is_none());

        let maybe_reserved_address = storage.next_expired_reserved_address(101)?;
        // Address expired, popped
        assert!(maybe_reserved_address.is_some());

        let maybe_reserved_address = storage.next_expired_reserved_address(102)?;
        // Address already popped
        assert!(maybe_reserved_address.is_none());

        Ok(())
    }

    #[sdk_macros::test_all]
    fn test_delete_reserved_address() -> Result<()> {
        create_persister!(storage);
        let address = "tlq1pq2amlulhea6ltq7x3eu9atsc2nnrer7yt7xve363zxedqwu2mk6ctcyv9awl8xf28cythreqklt5q0qqwsxzlm6wu4z6d574adl9zh2zmr0h85gt534n";

        storage.insert_or_update_reserved_address(address, 100)?;

        let maybe_reserved_address = storage.next_expired_reserved_address(99)?;
        // Under the expiry, not popped
        assert!(maybe_reserved_address.is_none());

        storage.delete_reserved_address(address)?;

        let maybe_reserved_address = storage.next_expired_reserved_address(101)?;
        // Over the expired, but already deleted
        assert!(maybe_reserved_address.is_none());

        Ok(())
    }
}