breez_sdk_liquid/sync/model/
mod.rs

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