breez_sdk_liquid/sync/model/
mod.rs

1tonic::include_proto!("sync");
2
3use self::data::SyncData;
4use crate::prelude::{Signer, SignerError};
5use anyhow::Result;
6use lazy_static::lazy_static;
7use log::trace;
8use lwk_wollet::hashes::hex::DisplayHex;
9use rusqlite::{
10    types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef},
11    ToSql,
12};
13use sdk_common::bitcoin::hashes::{sha256, Hash};
14use sdk_common::utils::Arc;
15use semver::Version;
16
17pub(crate) mod client;
18pub(crate) mod data;
19
20const MESSAGE_PREFIX: &[u8; 13] = b"realtimesync:";
21lazy_static! {
22    static ref CURRENT_SCHEMA_VERSION: Version = Version::parse("0.7.0").unwrap();
23}
24
25#[derive(Copy, Clone)]
26pub(crate) enum RecordType {
27    Receive = 0,
28    Send = 1,
29    Chain = 2,
30    LastDerivationIndex = 3,
31    PaymentDetails = 4,
32    Bolt12Offer = 5,
33}
34
35impl ToSql for RecordType {
36    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
37        Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
38    }
39}
40
41impl FromSql for RecordType {
42    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
43        match value {
44            ValueRef::Integer(i) => match i as u8 {
45                0 => Ok(Self::Receive),
46                1 => Ok(Self::Send),
47                2 => Ok(Self::Chain),
48                3 => Ok(Self::LastDerivationIndex),
49                4 => Ok(Self::PaymentDetails),
50                5 => Ok(Self::Bolt12Offer),
51                _ => Err(FromSqlError::OutOfRange(i)),
52            },
53            _ => Err(FromSqlError::InvalidType),
54        }
55    }
56}
57
58pub(crate) struct SyncState {
59    pub(crate) data_id: String,
60    pub(crate) record_id: String,
61    pub(crate) record_revision: u64,
62    pub(crate) is_local: bool,
63}
64
65pub(crate) struct SyncSettings {
66    pub(crate) remote_url: Option<String>,
67    pub(crate) latest_revision: Option<u64>,
68}
69
70pub(crate) struct SyncOutgoingChanges {
71    pub(crate) record_id: String,
72    pub(crate) data_id: String,
73    pub(crate) record_type: RecordType,
74    pub(crate) commit_time: u32,
75    pub(crate) updated_fields: Option<Vec<String>>,
76}
77
78pub(crate) struct DecryptedRecord {
79    pub(crate) revision: u64,
80    pub(crate) id: String,
81    #[allow(dead_code)]
82    pub(crate) schema_version: String,
83    pub(crate) data: SyncData,
84}
85
86pub(crate) struct DecryptionInfo {
87    pub(crate) new_sync_state: SyncState,
88    pub(crate) record: DecryptedRecord,
89    pub(crate) last_commit_time: Option<u32>,
90}
91
92#[derive(thiserror::Error, Debug)]
93pub(crate) enum PullError {
94    #[error("Record is not applicable: schema_version too high")]
95    SchemaNotApplicable,
96
97    #[error("Remote record revision is lower or equal to the persisted one. Skipping update.")]
98    AlreadyPersisted,
99
100    #[error("Could not sign outgoing payload: {err}")]
101    Signing { err: String },
102
103    #[error("Could not decrypt incoming record: {err}")]
104    Decryption { err: String },
105
106    #[error("Could not deserialize record data: {err}")]
107    Deserialization { err: String },
108
109    #[error("Remote record version could not be parsed: {err}")]
110    InvalidRecordVersion { err: String },
111
112    #[error("Could not contact remote: {err}")]
113    Network { err: String },
114
115    #[error("Could not call the persister: {err}")]
116    Persister { err: String },
117
118    #[error("Could not merge record with updated fields: {err}")]
119    Merge { err: String },
120
121    #[error("Could not recover record data from onchain: {err}")]
122    Recovery { err: String },
123}
124
125impl PullError {
126    pub(crate) fn signing(err: SignerError) -> Self {
127        Self::Signing {
128            err: err.to_string(),
129        }
130    }
131
132    pub(crate) fn decryption(err: SignerError) -> Self {
133        Self::Decryption {
134            err: err.to_string(),
135        }
136    }
137
138    pub(crate) fn deserialization(err: serde_json::Error) -> Self {
139        Self::Deserialization {
140            err: err.to_string(),
141        }
142    }
143
144    pub(crate) fn invalid_record_version(err: semver::Error) -> Self {
145        Self::InvalidRecordVersion {
146            err: err.to_string(),
147        }
148    }
149
150    pub(crate) fn network(err: anyhow::Error) -> Self {
151        Self::Network {
152            err: err.to_string(),
153        }
154    }
155
156    pub(crate) fn persister(err: anyhow::Error) -> Self {
157        Self::Persister {
158            err: err.to_string(),
159        }
160    }
161
162    pub(crate) fn merge(err: anyhow::Error) -> Self {
163        Self::Merge {
164            err: err.to_string(),
165        }
166    }
167
168    pub(crate) fn recovery(err: anyhow::Error) -> Self {
169        Self::Recovery {
170            err: err.to_string(),
171        }
172    }
173}
174
175#[derive(thiserror::Error, Debug)]
176pub(crate) enum PushError {
177    #[error("Received conflict status from remote")]
178    RecordConflict,
179
180    #[error("Could not sign outgoing payload: {err}")]
181    Signing { err: String },
182
183    #[error("Could not encrypt outgoing record: {err}")]
184    Encryption { err: String },
185
186    #[error("Could not serialize record data: {err}")]
187    Serialization { err: String },
188
189    #[error("Could not contact remote: {err}")]
190    Network { err: String },
191
192    #[error("Could not call the persister: {err}")]
193    Persister { err: String },
194
195    #[error("Push completed with too many failed records: succeded {succeded} total {total} recoverable {recoverable}")]
196    ExcessiveRecordConflicts {
197        succeded: usize,
198        total: usize,
199        recoverable: usize,
200    },
201}
202
203impl PushError {
204    pub(crate) fn signing(err: SignerError) -> Self {
205        Self::Signing {
206            err: err.to_string(),
207        }
208    }
209
210    pub(crate) fn encryption(err: SignerError) -> Self {
211        Self::Encryption {
212            err: err.to_string(),
213        }
214    }
215
216    pub(crate) fn serialization(err: serde_json::Error) -> Self {
217        Self::Serialization {
218            err: err.to_string(),
219        }
220    }
221
222    pub(crate) fn network(err: anyhow::Error) -> Self {
223        Self::Network {
224            err: err.to_string(),
225        }
226    }
227
228    pub(crate) fn persister(err: anyhow::Error) -> Self {
229        Self::Persister {
230            err: err.to_string(),
231        }
232    }
233}
234
235impl Record {
236    pub(crate) fn new(
237        data: SyncData,
238        revision: u64,
239        signer: Arc<Box<dyn Signer>>,
240    ) -> Result<Self, PushError> {
241        let id = Self::get_id_from_sync_data(&data);
242        let data = data.to_bytes().map_err(PushError::serialization)?;
243        trace!("About to encrypt sync data: {data:?}");
244        let data = signer.ecies_encrypt(data).map_err(PushError::encryption)?;
245        trace!("Got encrypted sync data: {data:?}");
246        let schema_version = CURRENT_SCHEMA_VERSION.to_string();
247        Ok(Self {
248            id,
249            revision,
250            schema_version,
251            data,
252        })
253    }
254
255    fn id(prefix: String, data_id: &str) -> String {
256        sha256::Hash::hash((prefix + ":" + data_id).as_bytes()).to_lower_hex_string()
257    }
258
259    pub(crate) fn get_id_from_sync_data(data: &SyncData) -> String {
260        let prefix = match data {
261            SyncData::Chain(_) => "chain-swap",
262            SyncData::Send(_) => "send-swap",
263            SyncData::Receive(_) => "receive-swap",
264            SyncData::LastDerivationIndex(_) => "derivation-index",
265            SyncData::PaymentDetails(_) => "payment-details",
266            SyncData::Bolt12Offer(_) => "bolt12-offer",
267        }
268        .to_string();
269        Self::id(prefix, data.id())
270    }
271
272    pub(crate) fn get_id_from_record_type(record_type: RecordType, data_id: &str) -> String {
273        let prefix = match record_type {
274            RecordType::Chain => "chain-swap",
275            RecordType::Send => "send-swap",
276            RecordType::Receive => "receive-swap",
277            RecordType::LastDerivationIndex => "derivation-index",
278            RecordType::PaymentDetails => "payment-details",
279            RecordType::Bolt12Offer => "bolt12-offer",
280        }
281        .to_string();
282        Self::id(prefix, data_id)
283    }
284
285    pub(crate) fn is_applicable(&self) -> Result<bool, semver::Error> {
286        let record_version = Version::parse(&self.schema_version)?;
287        Ok(CURRENT_SCHEMA_VERSION.major >= record_version.major)
288    }
289
290    pub(crate) fn decrypt(
291        self,
292        signer: Arc<Box<dyn Signer>>,
293    ) -> Result<DecryptedRecord, PullError> {
294        let dec_data = signer
295            .ecies_decrypt(self.data)
296            .map_err(PullError::decryption)?;
297        let data = serde_json::from_slice(&dec_data).map_err(PullError::deserialization)?;
298        Ok(DecryptedRecord {
299            id: self.id,
300            revision: self.revision,
301            schema_version: self.schema_version,
302            data,
303        })
304    }
305}