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