bark/persist/mod.rs
1//! Persistence abstractions for Bark wallets.
2//!
3//! This module defines the [BarkPersister] trait and related data models used by the
4//! wallet to store and retrieve state. Implementors can provide their own storage backends
5//! (e.g., SQLite, PostgreSQL, in-memory, mobile key/value stores) by implementing the
6//! [BarkPersister] trait.
7//!
8//! Design goals
9//! - Clear separation between wallet logic and storage.
10//! - Transactional semantics where appropriate (round state transitions, movement recording).
11//! - Portability across different platforms and environments.
12//!
13//! Typical usage
14//! - Applications construct a concrete persister (for example, a SQLite-backed client) and
15//! pass it to the [crate::Wallet]. The [crate::Wallet] only depends on this trait for reads/writes.
16//! - Custom wallet implementations can reuse this trait to remain compatible with Bark
17//! storage expectations without depending on a specific database.
18//! - A default rusqlite implementation is provided by [sqlite::SqliteClient].
19
20pub mod models;
21pub mod sqlite;
22
23
24use std::fmt;
25
26use bitcoin::{Amount, Transaction, Txid};
27use bitcoin::secp256k1::PublicKey;
28use chrono::DateTime;
29use lightning_invoice::Bolt11Invoice;
30#[cfg(feature = "onchain_bdk")]
31use bdk_wallet::ChangeSet;
32
33use ark::{Vtxo, VtxoId};
34use ark::lightning::{Invoice, PaymentHash, Preimage};
35use bitcoin_ext::BlockDelta;
36
37use crate::WalletProperties;
38use crate::exit::models::ExitTxOrigin;
39use crate::movement::{Movement, MovementId, MovementStatus, MovementSubsystem};
40use crate::persist::models::{LightningReceive, LightningSend, PendingBoard, StoredExit};
41use crate::round::{RoundState, UnconfirmedRound};
42use crate::vtxo::state::{VtxoState, VtxoStateKind, WalletVtxo};
43
44/// Identifier for a stored [RoundState].
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct RoundStateId(pub u32);
47
48impl fmt::Display for RoundStateId {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 fmt::Display::fmt(&self.0, f)
51 }
52}
53
54pub struct StoredRoundState {
55 pub id: RoundStateId,
56 pub state: RoundState,
57}
58
59/// Storage interface for Bark wallets.
60///
61/// Implement this trait to plug a custom persistence backend. The wallet uses it to:
62/// - Initialize and read wallet properties and configuration.
63/// - Record movements (spends/receives), recipients, and enforce [Vtxo] state transitions.
64/// - Manage round lifecycles (attempts, pending confirmation, confirmations/cancellations).
65/// - Persist ephemeral protocol artifacts (e.g., secret nonces) transactionally.
66/// - Track Lightning receives and preimage revelation.
67/// - Track exit-related data and associated child transactions.
68/// - Persist the last synchronized Ark block height.
69///
70/// Feature integration:
71/// - With the `onchain_bdk` feature, methods are provided to initialize and persist a BDK
72/// wallet ChangeSet in the same storage.
73///
74/// Notes for implementors:
75/// - Ensure that operations that change multiple records (e.g., registering a movement,
76/// storing round state transitions) are executed transactionally.
77/// - Enforce state integrity by verifying allowed_old_states before updating a [Vtxo] state.
78/// - If your backend is not thread-safe, prefer a short-lived connection per call or use
79/// an internal pool with checked-out connections per operation.
80/// - Return precise errors so callers can surface actionable diagnostics.
81pub trait BarkPersister: Send + Sync + 'static {
82 /// Initialize a wallet in storage with the provided properties.
83 ///
84 /// Call exactly once per wallet database. Subsequent calls should fail to prevent
85 /// accidental re-initialization.
86 ///
87 /// Parameters:
88 /// - properties: WalletProperties to persist (e.g., network, descriptors, metadata).
89 ///
90 /// Returns:
91 /// - `Ok(())` on success.
92 ///
93 /// Errors:
94 /// - Returns an error if the wallet is already initialized or if persistence fails.
95 fn init_wallet(&self, properties: &WalletProperties) -> anyhow::Result<()>;
96
97 /// Initialize the onchain BDK wallet and return any previously stored ChangeSet.
98 ///
99 /// Must be called before storing any new BDK changesets to bootstrap the BDK state.
100 ///
101 /// Feature: only available with `onchain_bdk`.
102 ///
103 /// Returns:
104 /// - `Ok(ChangeSet)` containing the previously persisted BDK state (possibly empty).
105 ///
106 /// Errors:
107 /// - Returns an error if the BDK state cannot be created or loaded.
108 #[cfg(feature = "onchain_bdk")]
109 fn initialize_bdk_wallet(&self) -> anyhow::Result<ChangeSet>;
110
111 /// Persist an incremental BDK ChangeSet.
112 ///
113 /// The changeset should be applied atomically. Callers typically obtain the changeset
114 /// from a BDK wallet instance after mutating wallet state (e.g., sync).
115 ///
116 /// Feature: only available with `onchain_bdk`.
117 ///
118 /// Parameters:
119 /// - changeset: The BDK ChangeSet to persist.
120 ///
121 /// Errors:
122 /// - Returns an error if the changeset cannot be written.
123 #[cfg(feature = "onchain_bdk")]
124 fn store_bdk_wallet_changeset(&self, changeset: &ChangeSet) -> anyhow::Result<()>;
125
126 /// Read wallet properties from storage.
127 ///
128 /// Returns:
129 /// - `Ok(Some(WalletProperties))` if the wallet has been initialized.
130 /// - `Ok(None)` if no wallet exists yet.
131 ///
132 /// Errors:
133 /// - Returns an error on I/O or deserialization failures.
134 fn read_properties(&self) -> anyhow::Result<Option<WalletProperties>>;
135
136 /// Check whether a recipient identifier already exists.
137 ///
138 /// Useful to avoid storing duplicate recipients for the same logical payee or duplicated
139 /// lightning invoice payments (unsafe)
140 ///
141 /// Parameters:
142 /// - recipient: A recipient identifier (e.g., invoice).
143 ///
144 /// Returns:
145 /// - `Ok(true)` if the recipient exists,
146 /// - `Ok(false)` otherwise.
147 ///
148 /// Errors:
149 /// - Returns an error if the lookup fails.
150 fn check_recipient_exists(&self, recipient: &str) -> anyhow::Result<bool>;
151
152 /// Creates a new movement in the given state, ready to be updated.
153 ///
154 /// Parameters:
155 /// - status: The desired status for the new movement.
156 /// - subsystem: The subsystem that created the movement.
157 /// - time: The time the movement should be marked as created.
158 ///
159 /// Returns:
160 /// - `Ok(MovementId)` of the newly created movement.
161 ///
162 /// Errors:
163 /// - Returns an error if the movement is unable to be created.
164 fn create_new_movement(&self,
165 status: MovementStatus,
166 subsystem: &MovementSubsystem,
167 time: DateTime<chrono::Local>,
168 ) -> anyhow::Result<MovementId>;
169
170 /// Persists the given movement state.
171 ///
172 /// Parameters:
173 /// - movement: The movement and its associated data to be persisted.
174 ///
175 /// Errors:
176 /// - Returns an error if updating the movement fails for any reason.
177 fn update_movement(&self, movement: &Movement) -> anyhow::Result<()>;
178
179 /// Gets the movement with the given [MovementId].
180 ///
181 /// Parameters:
182 /// - movement_id: The ID of the movement to retrieve.
183 ///
184 /// Returns:
185 /// - `Ok(Movement)` if the movement exists.
186 ///
187 /// Errors:
188 /// - If the movement does not exist.
189 /// - If retrieving the movement fails.
190 fn get_movement_by_id(&self, movement_id: MovementId) -> anyhow::Result<Movement>;
191
192 /// Gets every stored movement.
193 ///
194 /// Returns:
195 /// - `Ok(Vec<Movement>)` containing all movements, empty if none exist.
196 ///
197 /// Errors:
198 /// - If retrieving the movements fails.
199 fn get_all_movements(&self) -> anyhow::Result<Vec<Movement>>;
200
201 /// Store a pending board.
202 ///
203 /// Parameters:
204 /// - vtxo: The [Vtxo] to store.
205 /// - funding_txid: The funding [Txid].
206 /// - movement_id: The [MovementId] associated with this board.
207 ///
208 /// Errors:
209 /// - Returns an error if the board cannot be stored.
210 fn store_pending_board(
211 &self,
212 vtxo: &Vtxo,
213 funding_tx: &Transaction,
214 movement_id: MovementId,
215 ) -> anyhow::Result<()>;
216
217 /// Remove a pending board.
218 ///
219 /// Parameters:
220 /// - vtxo_id: The [VtxoId] to remove.
221 ///
222 /// Errors:
223 /// - Returns an error if the board cannot be removed.
224 fn remove_pending_board(&self, vtxo_id: &VtxoId) -> anyhow::Result<()>;
225
226 /// Get the [VtxoId] for each pending board.
227 ///
228 /// Returns:
229 /// - `Ok(Vec<VtxoId>)` possibly empty.
230 ///
231 /// Errors:
232 /// - Returns an error if the query fails.
233 fn get_all_pending_board_ids(&self) -> anyhow::Result<Vec<VtxoId>>;
234
235 /// Get the [PendingBoard] associated with the given [VtxoId].
236 ///
237 /// Returns:
238 /// - `Ok(Some(PendingBoard))` if a matching board exists
239 /// - `Ok(None)` if no matching board exists
240 ///
241 /// Errors:
242 /// - Returns an error if the query fails.
243 fn get_pending_board_by_vtxo_id(&self, vtxo_id: VtxoId) -> anyhow::Result<Option<PendingBoard>>;
244
245 /// Store a new ongoing round state and lock the VTXOs in round
246 ///
247 /// Parameters:
248 /// - `round_state`: the state to store
249 ///
250 /// Returns:
251 /// - `RoundStateId`: the storaged ID of the new state
252 ///
253 /// Errors:
254 /// - returns an error of the new round state could not be stored or the VTXOs
255 /// couldn't be marked as locked
256 fn store_round_state_lock_vtxos(&self, round_state: &RoundState) -> anyhow::Result<RoundStateId>;
257
258 /// Update an existing stored pending round state
259 ///
260 /// Parameters:
261 /// - `round_state`: the round state to update
262 ///
263 /// Errors:
264 /// - returns an error of the existing round state could not be found or updated
265 fn update_round_state(&self, round_state: &StoredRoundState) -> anyhow::Result<()>;
266
267 /// Remove a pending round state from the db
268 ///
269 /// Parameters:
270 /// - `round_state`: the round state to remove
271 ///
272 /// Errors:
273 /// - returns an error of the existing round state could not be found or removed
274 fn remove_round_state(&self, round_state: &StoredRoundState) -> anyhow::Result<()>;
275
276 /// Load all pending round states from the db
277 ///
278 /// Returns:
279 /// - `Vec<StoredRoundState>`: unordered vector with all stored round states
280 ///
281 /// Errors:
282 /// - returns an error of the states could not be succesfully retrieved
283 fn load_round_states(&self) -> anyhow::Result<Vec<StoredRoundState>>;
284
285 /// Store a recovered past round
286 fn store_recovered_round(&self, round: &UnconfirmedRound) -> anyhow::Result<()>;
287
288 /// Remove a recovered past round
289 fn remove_recovered_round(&self, funding_txid: Txid) -> anyhow::Result<()>;
290
291 /// Load the recovered past rounds
292 fn load_recovered_rounds(&self) -> anyhow::Result<Vec<UnconfirmedRound>>;
293
294 /// Stores the given VTXOs in the given [VtxoState].
295 fn store_vtxos(
296 &self,
297 vtxos: &[(&Vtxo, &VtxoState)],
298 ) -> anyhow::Result<()>;
299
300 /// Fetch a wallet [Vtxo] with its current state by ID.
301 ///
302 /// Parameters:
303 /// - id: [VtxoId] to look up.
304 ///
305 /// Returns:
306 /// - `Ok(Some(WalletVtxo))` if found,
307 /// - `Ok(None)` otherwise.
308 ///
309 /// Errors:
310 /// - Returns an error if the lookup fails.
311 fn get_wallet_vtxo(&self, id: VtxoId) -> anyhow::Result<Option<WalletVtxo>>;
312
313 /// Fetch all wallet VTXOs in the database.
314 ///
315 /// Returns:
316 /// - `Ok(Vec<WalletVtxo>)` possibly empty.
317 ///
318 /// Errors:
319 /// - Returns an error if the query fails.
320 fn get_all_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>>;
321
322 /// Fetch all wallet VTXOs whose state matches any of the provided kinds.
323 ///
324 /// Parameters:
325 /// - state: Slice of `VtxoStateKind` filters.
326 ///
327 /// Returns:
328 /// - `Ok(Vec<WalletVtxo>)` possibly empty.
329 ///
330 /// Errors:
331 /// - Returns an error if the query fails.
332 fn get_vtxos_by_state(&self, state: &[VtxoStateKind]) -> anyhow::Result<Vec<WalletVtxo>>;
333
334 /// Remove a [Vtxo] by ID.
335 ///
336 /// Parameters:
337 /// - id: `VtxoId` to remove.
338 ///
339 /// Returns:
340 /// - `Ok(Some(Vtxo))` with the removed [Vtxo] data if it existed,
341 /// - `Ok(None)` otherwise.
342 ///
343 /// Errors:
344 /// - Returns an error if the delete operation fails.
345 fn remove_vtxo(&self, id: VtxoId) -> anyhow::Result<Option<Vtxo>>;
346
347 /// Check whether a [Vtxo] is already marked spent.
348 ///
349 /// Parameters:
350 /// - id: VtxoId to check.
351 ///
352 /// Returns:
353 /// - `Ok(true)` if spent,
354 /// - `Ok(false)` if not found or not spent.
355 ///
356 /// Errors:
357 /// - Returns an error if the lookup fails.
358 fn has_spent_vtxo(&self, id: VtxoId) -> anyhow::Result<bool>;
359
360 /// Store a newly derived/assigned [Vtxo] public key index mapping.
361 ///
362 /// Parameters:
363 /// - index: Derivation index.
364 /// - public_key: PublicKey at that index.
365 ///
366 /// Errors:
367 /// - Returns an error if the mapping cannot be stored.
368 fn store_vtxo_key(&self, index: u32, public_key: PublicKey) -> anyhow::Result<()>;
369
370 /// Get the last revealed/used [Vtxo] key index.
371 ///
372 /// Returns:
373 /// - `Ok(Some(u32))` if a key was stored
374 /// - `Ok(None)` otherwise.
375 ///
376 /// Errors:
377 /// - Returns an error if the query fails.
378 fn get_last_vtxo_key_index(&self) -> anyhow::Result<Option<u32>>;
379
380 /// Retrieves the derivation index of the provided [PublicKey] from the database
381 ///
382 /// Returns:
383 /// - `Ok(Some(u32))` if the key was stored.
384 /// - `Ok(None)` if the key was not stored.
385 ///
386 /// Errors:
387 /// - Returns an error if the query fails.
388 fn get_public_key_idx(&self, public_key: &PublicKey) -> anyhow::Result<Option<u32>>;
389
390 /// Store a new pending lightning send.
391 ///
392 /// Parameters:
393 /// - invoice: The invoice of the pending lightning send.
394 /// - amount: The amount of the pending lightning send.
395 /// - vtxos: The vtxos of the pending lightning send.
396 ///
397 /// Errors:
398 /// - Returns an error if the pending lightning send cannot be stored.
399 fn store_new_pending_lightning_send(
400 &self,
401 invoice: &Invoice,
402 amount: &Amount,
403 vtxos: &[VtxoId],
404 movement_id: MovementId,
405 ) -> anyhow::Result<LightningSend>;
406
407 /// Get all pending lightning sends.
408 ///
409 /// Returns:
410 /// - `Ok(Vec<LightningSend>)` possibly empty.
411 ///
412 /// Errors:
413 /// - Returns an error if the query fails.
414 fn get_all_pending_lightning_send(&self) -> anyhow::Result<Vec<LightningSend>>;
415
416 /// Mark a lightning send as finished.
417 ///
418 /// Parameters:
419 /// - payment_hash: The [PaymentHash] of the lightning send to update.
420 /// - preimage: The [Preimage] of the successful lightning send.
421 ///
422 /// Errors:
423 /// - Returns an error if the lightning send cannot be updated.
424 fn finish_lightning_send(
425 &self,
426 payment_hash: PaymentHash,
427 preimage: Option<Preimage>,
428 ) -> anyhow::Result<()>;
429
430 /// Remove a lightning send.
431 ///
432 /// Parameters:
433 /// - payment_hash: The [PaymentHash] of the lightning send to remove.
434 ///
435 /// Errors:
436 /// - Returns an error if the lightning send cannot be removed.
437 fn remove_lightning_send(&self, payment_hash: PaymentHash) -> anyhow::Result<()>;
438
439 /// Get a lightning send by payment hash
440 ///
441 /// Parameters:
442 /// - payment_hash: The [PaymentHash] of the lightning send to get.
443 ///
444 /// Errors:
445 /// - Returns an error if the lookup fails.
446 fn get_lightning_send(&self, payment_hash: PaymentHash) -> anyhow::Result<Option<LightningSend>>;
447
448 /// Store an incoming Lightning receive record.
449 ///
450 /// Parameters:
451 /// - payment_hash: Unique payment hash.
452 /// - preimage: Payment preimage (kept until disclosure).
453 /// - invoice: The associated BOLT11 invoice.
454 /// - htlc_recv_cltv_delta: The CLTV delta for the HTLC VTXO.
455 ///
456 /// Errors:
457 /// - Returns an error if the receive cannot be stored.
458 fn store_lightning_receive(
459 &self,
460 payment_hash: PaymentHash,
461 preimage: Preimage,
462 invoice: &Bolt11Invoice,
463 htlc_recv_cltv_delta: BlockDelta,
464 ) -> anyhow::Result<()>;
465
466 /// Returns a list of all pending lightning receives
467 ///
468 /// Returns:
469 /// - `Ok(Vec<LightningReceive>)` possibly empty.
470 ///
471 /// Errors:
472 /// - Returns an error if the query fails.
473 fn get_all_pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>>;
474
475 /// Mark a Lightning receive preimage as revealed (e.g., after settlement).
476 ///
477 /// Parameters:
478 /// - payment_hash: The payment hash identifying the receive.
479 ///
480 /// Errors:
481 /// - Returns an error if the update fails or the receive does not exist.
482 fn set_preimage_revealed(&self, payment_hash: PaymentHash) -> anyhow::Result<()>;
483
484 /// Set the VTXO IDs and [MovementId] for a [LightningReceive].
485 ///
486 /// Parameters:
487 /// - payment_hash: The payment hash identifying the receive.
488 /// - htlc_vtxo_ids: The VTXO IDs to set.
489 /// - movement_id: The movement ID associated with the invoice.
490 ///
491 /// Errors:
492 /// - Returns an error if the update fails or the receive does not exist.
493 fn update_lightning_receive(
494 &self,
495 payment_hash: PaymentHash,
496 htlc_vtxo_ids: &[VtxoId],
497 movement_id: MovementId,
498 ) -> anyhow::Result<()>;
499
500 /// Fetch a Lightning receive by its payment hash.
501 ///
502 /// Parameters:
503 /// - payment_hash: The payment hash to look up.
504 ///
505 /// Returns:
506 /// - `Ok(Some(LightningReceive))` if found,
507 /// - `Ok(None)` otherwise.
508 ///
509 /// Errors:
510 /// - Returns an error if the lookup fails.
511 fn fetch_lightning_receive_by_payment_hash(
512 &self,
513 payment_hash: PaymentHash,
514 ) -> anyhow::Result<Option<LightningReceive>>;
515
516 /// Remove a Lightning receive by its payment hash.
517 ///
518 /// Parameters:
519 /// - payment_hash: The payment hash of the record to remove.
520 ///
521 /// Errors:
522 /// - Returns an error if the removal fails.
523 fn finish_pending_lightning_receive(&self, payment_hash: PaymentHash) -> anyhow::Result<()>;
524
525 /// Store an entry indicating a [Vtxo] is being exited.
526 ///
527 /// Parameters:
528 /// - exit: StoredExit describing the exit operation.
529 ///
530 /// Errors:
531 /// - Returns an error if the entry cannot be stored.
532 fn store_exit_vtxo_entry(&self, exit: &StoredExit) -> anyhow::Result<()>;
533
534 /// Remove an exit entry for a given [Vtxo] ID.
535 ///
536 /// Parameters:
537 /// - id: VtxoId to remove from exit tracking.
538 ///
539 /// Errors:
540 /// - Returns an error if the removal fails.
541 fn remove_exit_vtxo_entry(&self, id: &VtxoId) -> anyhow::Result<()>;
542
543 /// List all VTXOs currently tracked as being exited.
544 ///
545 /// Returns:
546 /// - `Ok(Vec<StoredExit>)` possibly empty.
547 ///
548 /// Errors:
549 /// - Returns an error if the query fails.
550 fn get_exit_vtxo_entries(&self) -> anyhow::Result<Vec<StoredExit>>;
551
552 /// Store a child transaction related to an exit transaction.
553 ///
554 /// Parameters:
555 /// - exit_txid: The parent exit transaction ID.
556 /// - child_tx: The child bitcoin Transaction to store.
557 /// - origin: Metadata describing where the child came from (ExitTxOrigin).
558 ///
559 /// Errors:
560 /// - Returns an error if the transaction cannot be stored.
561 fn store_exit_child_tx(
562 &self,
563 exit_txid: Txid,
564 child_tx: &Transaction,
565 origin: ExitTxOrigin,
566 ) -> anyhow::Result<()>;
567
568 /// Retrieve a stored child transaction for a given exit transaction ID.
569 ///
570 /// Parameters:
571 /// - exit_txid: The parent exit transaction ID.
572 ///
573 /// Returns:
574 /// - `Ok(Some((Transaction, ExitTxOrigin)))` if found,
575 /// - `Ok(None)` otherwise.
576 ///
577 /// Errors:
578 /// - Returns an error if the lookup fails.
579 fn get_exit_child_tx(
580 &self,
581 exit_txid: Txid,
582 ) -> anyhow::Result<Option<(Transaction, ExitTxOrigin)>>;
583
584 /// Updates the state of the VTXO corresponding to the given [VtxoId], provided that their
585 /// current state is one of the given `allowed_states`.
586 ///
587 /// # Parameters
588 /// - `vtxo_id`: The ID of the [Vtxo] to update.
589 /// - `state`: The new state to be set for the specified [Vtxo].
590 /// - `allowed_states`: An iterable collection of allowed states ([VtxoStateKind]) that the
591 /// [Vtxo] must currently be in for their state to be updated to the new `state`.
592 ///
593 /// # Returns
594 /// - `Ok(WalletVtxo)` if the state update is successful.
595 /// - `Err(anyhow::Error)` if the VTXO fails to meet the required conditions,
596 /// or if another error occurs during the operation.
597 ///
598 /// # Errors
599 /// - Returns an error if the current state is not within the `allowed_states`.
600 /// - Returns an error for any other issues encountered during the operation.
601 fn update_vtxo_state_checked(
602 &self,
603 vtxo_id: VtxoId,
604 new_state: VtxoState,
605 allowed_old_states: &[VtxoStateKind],
606 ) -> anyhow::Result<WalletVtxo>;
607}