breez_sdk_liquid/sync/model/
mod.rs1tonic::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}