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}